loading
Generated 2024-07-17T13:51:48+05:30

All Files ( 60.95% covered at 78.39 hits/line )

384 files in total.
10971 relevant lines, 6687 lines covered and 4284 lines missed. ( 60.95% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/controllers/admin/admin_controller.rb 100.00 % 8 4 4 0 1.00
app/controllers/admin/pods_controller.rb 100.00 % 36 22 22 0 2.14
app/controllers/admin/users_controller.rb 30.91 % 92 55 17 38 0.31
app/controllers/admins_controller.rb 87.50 % 152 80 70 10 3.08
app/controllers/api/openid_connect/authorizations_controller.rb 91.88 % 262 160 147 13 9.94
app/controllers/api/openid_connect/clients_controller.rb 82.14 % 56 28 23 5 1.50
app/controllers/api/openid_connect/discovery_controller.rb 100.00 % 52 5 5 0 1.40
app/controllers/api/openid_connect/id_tokens_controller.rb 100.00 % 38 8 8 0 1.00
app/controllers/api/openid_connect/token_endpoint_controller.rb 95.12 % 65 41 39 2 6.80
app/controllers/api/openid_connect/user_applications_controller.rb 100.00 % 13 6 6 0 1.00
app/controllers/api/openid_connect/user_info_controller.rb 100.00 % 28 15 15 0 3.07
app/controllers/api/v1/aspects_controller.rb 97.73 % 79 44 43 1 2.41
app/controllers/api/v1/base_controller.rb 79.07 % 77 43 34 9 62.65
app/controllers/api/v1/comments_controller.rb 100.00 % 118 67 67 0 4.84
app/controllers/api/v1/contacts_controller.rb 100.00 % 62 34 34 0 2.88
app/controllers/api/v1/conversations_controller.rb 100.00 % 87 50 50 0 14.56
app/controllers/api/v1/likes_controller.rb 100.00 % 138 72 72 0 5.08
app/controllers/api/v1/messages_controller.rb 100.00 % 43 25 25 0 2.12
app/controllers/api/v1/notifications_controller.rb 96.67 % 57 30 29 1 3.37
app/controllers/api/v1/photos_controller.rb 97.62 % 76 42 41 1 3.45
app/controllers/api/v1/post_interactions_controller.rb 98.08 % 101 52 51 1 6.23
app/controllers/api/v1/posts_controller.rb 100.00 % 115 63 63 0 7.30
app/controllers/api/v1/reshares_controller.rb 96.00 % 52 25 24 1 2.28
app/controllers/api/v1/search_controller.rb 100.00 % 116 65 65 0 8.14
app/controllers/api/v1/streams_controller.rb 97.50 % 74 40 39 1 8.28
app/controllers/api/v1/tag_followings_controller.rb 100.00 % 41 21 21 0 2.14
app/controllers/api/v1/users_controller.rb 96.47 % 145 85 82 3 2.88
app/controllers/application_controller.rb 100.00 % 165 82 82 0 243.41
app/controllers/aspect_memberships_controller.rb 83.33 % 65 30 25 5 1.60
app/controllers/aspects_controller.rb 93.62 % 94 47 44 3 3.02
app/controllers/blocks_controller.rb 100.00 % 42 20 20 0 3.85
app/controllers/comments_controller.rb 93.18 % 86 44 41 3 2.70
app/controllers/contacts_controller.rb 88.89 % 107 54 48 6 5.33
app/controllers/conversation_visibilities_controller.rb 100.00 % 26 11 11 0 3.09
app/controllers/conversations_controller.rb 95.24 % 121 63 60 3 9.19
app/controllers/help_controller.rb 100.00 % 4 1 1 0 1.00
app/controllers/home_controller.rb 90.91 % 46 22 20 2 1.59
app/controllers/invitation_codes_controller.rb 100.00 % 25 13 13 0 1.23
app/controllers/invitations_controller.rb 100.00 % 78 42 42 0 5.57
app/controllers/likes_controller.rb 92.59 % 59 27 25 2 2.44
app/controllers/links_controller.rb 100.00 % 16 8 8 0 3.25
app/controllers/manifest_controller.rb 0.00 % 27 25 0 25 0.00
app/controllers/messages_controller.rb 100.00 % 28 13 13 0 4.54
app/controllers/node_info_controller.rb 92.86 % 33 14 13 1 3.43
app/controllers/notifications_controller.rb 100.00 % 98 50 50 0 11.92
app/controllers/participations_controller.rb 100.00 % 25 14 14 0 1.86
app/controllers/people_controller.rb 100.00 % 168 88 88 0 12.05
app/controllers/photos_controller.rb 80.22 % 177 91 73 18 4.56
app/controllers/poll_participations_controller.rb 100.00 % 24 13 13 0 2.08
app/controllers/posts_controller.rb 100.00 % 76 42 42 0 4.98
app/controllers/profiles_controller.rb 95.12 % 84 41 39 2 5.49
app/controllers/registrations_controller.rb 96.43 % 57 28 27 1 6.75
app/controllers/report_controller.rb 86.36 % 44 22 19 3 1.77
app/controllers/reshares_controller.rb 100.00 % 26 12 12 0 2.67
app/controllers/search_controller.rb 100.00 % 28 14 14 0 2.57
app/controllers/services_controller.rb 90.20 % 100 51 46 5 2.67
app/controllers/sessions_controller.rb 41.67 % 52 24 10 14 0.67
app/controllers/share_visibilities_controller.rb 100.00 % 22 9 9 0 1.89
app/controllers/status_messages_controller.rb 88.00 % 111 50 44 6 5.72
app/controllers/streams_controller.rb 88.37 % 87 43 38 5 6.28
app/controllers/tag_followings_controller.rb 87.50 % 55 24 21 3 2.04
app/controllers/tags_controller.rb 100.00 % 76 41 41 0 3.95
app/controllers/terms_controller.rb 85.71 % 21 7 6 1 1.29
app/controllers/two_factor_authentications_controller.rb 100.00 % 55 32 32 0 1.16
app/controllers/users_controller.rb 81.34 % 246 134 109 25 5.76
app/helpers/activity_streams_helper.rb 0.00 % 14 11 0 11 0.00
app/helpers/application_helper.rb 0.00 % 88 66 0 66 0.00
app/helpers/aspect_global_helper.rb 0.00 % 28 21 0 21 0.00
app/helpers/contacts_helper.rb 0.00 % 14 11 0 11 0.00
app/helpers/conversations_helper.rb 0.00 % 10 7 0 7 0.00
app/helpers/error_messages_helper.rb 0.00 % 28 19 0 19 0.00
app/helpers/getting_started_helper.rb 0.00 % 12 5 0 5 0.00
app/helpers/gon_helper.rb 0.00 % 10 8 0 8 0.00
app/helpers/interim_stream_hackiness_helper.rb 0.00 % 22 18 0 18 0.00
app/helpers/invitation_codes_helper.rb 0.00 % 21 17 0 17 0.00
app/helpers/language_helper.rb 0.00 % 31 24 0 24 0.00
app/helpers/layout_helper.rb 0.00 % 63 44 0 44 0.00
app/helpers/meta_data_helper.rb 0.00 % 55 42 0 42 0.00
app/helpers/mobile_helper.rb 0.00 % 95 83 0 83 0.00
app/helpers/notifications_helper.rb 0.00 % 114 97 0 97 0.00
app/helpers/notifier_helper.rb 0.00 % 24 14 0 14 0.00
app/helpers/o_embed_helper.rb 0.00 % 35 30 0 30 0.00
app/helpers/open_graph_helper.rb 0.00 % 11 8 0 8 0.00
app/helpers/people_helper.rb 0.00 % 77 59 0 59 0.00
app/helpers/posts_helper.rb 0.00 % 28 21 0 21 0.00
app/helpers/profile_helper.rb 0.00 % 17 10 0 10 0.00
app/helpers/publisher_helper.rb 0.00 % 49 36 0 36 0.00
app/helpers/report_helper.rb 0.00 % 25 18 0 18 0.00
app/helpers/services_helper.rb 0.00 % 13 9 0 9 0.00
app/helpers/sessions_helper.rb 0.00 % 20 15 0 15 0.00
app/helpers/statistics_helper.rb 0.00 % 39 30 0 30 0.00
app/helpers/stream_helper.rb 0.00 % 58 47 0 47 0.00
app/helpers/tags_helper.rb 0.00 % 18 14 0 14 0.00
app/helpers/user_applications_helper.rb 0.00 % 11 9 0 9 0.00
app/helpers/users_helper.rb 0.00 % 46 22 0 22 0.00
app/mailers/application_mailer.rb 100.00 % 5 2 2 0 1.00
app/mailers/diaspora_devise_mailer.rb 75.00 % 9 4 3 1 0.75
app/mailers/export_mailer.rb 100.00 % 32 14 14 0 9.50
app/mailers/maintenance.rb 100.00 % 17 7 7 0 7.29
app/mailers/notification_mailers/also_commented.rb 100.00 % 25 13 13 0 7.85
app/mailers/notification_mailers/base.rb 100.00 % 68 34 34 0 518.24
app/mailers/notification_mailers/comment_on_post.rb 100.00 % 18 9 9 0 11.11
app/mailers/notification_mailers/confirm_email.rb 100.00 % 10 5 5 0 3.00
app/mailers/notification_mailers/contacts_birthday.rb 100.00 % 12 6 6 0 1.67
app/mailers/notification_mailers/csrf_token_fail.rb 100.00 % 9 4 4 0 2.00
app/mailers/notification_mailers/liked.rb 100.00 % 15 8 8 0 6.25
app/mailers/notification_mailers/liked_comment.rb 100.00 % 16 8 8 0 4.38
app/mailers/notification_mailers/mentioned.rb 100.00 % 15 8 8 0 41.13
app/mailers/notification_mailers/mentioned_in_comment.rb 100.00 % 14 7 7 0 9.14
app/mailers/notification_mailers/private_message.rb 100.00 % 16 9 9 0 4.33
app/mailers/notification_mailers/reshared.rb 100.00 % 16 8 8 0 2.88
app/mailers/notification_mailers/started_sharing.rb 100.00 % 9 4 4 0 129.25
app/mailers/notifier.rb 100.00 % 66 35 35 0 90.51
app/mailers/report_mailer.rb 100.00 % 35 19 19 0 12.05
app/models/account_deletion.rb 100.00 % 33 15 15 0 2.93
app/models/account_migration.rb 100.00 % 278 133 133 0 128.70
app/models/api/openid_connect/authorization.rb 0.00 % 112 92 0 92 0.00
app/models/api/openid_connect/o_auth_access_token.rb 100.00 % 50 13 13 0 455.15
app/models/api/openid_connect/o_auth_application.rb 0.00 % 128 88 0 88 0.00
app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb 100.00 % 46 12 12 0 1.67
app/models/application_record.rb 0.00 % 5 3 0 3 0.00
app/models/aspect.rb 95.00 % 45 20 19 1 208.55
app/models/aspect_membership.rb 81.82 % 27 11 9 2 13.55
app/models/aspect_visibility.rb 100.00 % 14 4 4 0 1.00
app/models/block.rb 100.00 % 23 11 11 0 8.64
app/models/comment.rb 100.00 % 83 46 46 0 104.50
app/models/comment_signature.rb 100.00 % 8 4 4 0 1.00
app/models/contact.rb 95.56 % 104 45 43 2 250.51
app/models/conversation.rb 100.00 % 75 42 42 0 43.57
app/models/conversation_visibility.rb 100.00 % 18 9 9 0 8.33
app/models/invitation_code.rb 95.65 % 44 23 22 1 14.83
app/models/like.rb 100.00 % 48 26 26 0 42.81
app/models/like_signature.rb 100.00 % 8 4 4 0 1.00
app/models/location.rb 100.00 % 16 8 8 0 3.38
app/models/mention.rb 0.00 % 20 11 0 11 0.00
app/models/message.rb 100.00 % 44 24 24 0 15.54
app/models/notification.rb 0.00 % 58 40 0 40 0.00
app/models/notification_actor.rb 100.00 % 10 3 3 0 1.00
app/models/notifications/also_commented.rb 0.00 % 27 20 0 20 0.00
app/models/notifications/comment_on_post.rb 92.31 % 25 13 12 1 8.54
app/models/notifications/commented.rb 0.00 % 17 13 0 13 0.00
app/models/notifications/contacts_birthday.rb 100.00 % 19 10 10 0 1.80
app/models/notifications/liked.rb 100.00 % 26 13 13 0 14.46
app/models/notifications/liked_comment.rb 69.23 % 26 13 9 4 1.15
app/models/notifications/mentioned.rb 100.00 % 29 13 13 0 273.15
app/models/notifications/mentioned_in_comment.rb 94.74 % 35 19 18 1 4.26
app/models/notifications/mentioned_in_post.rb 91.67 % 24 12 11 1 140.75
app/models/notifications/private_message.rb 93.33 % 30 15 14 1 2.93
app/models/notifications/reshared.rb 83.33 % 24 12 10 2 3.33
app/models/notifications/started_sharing.rb 100.00 % 22 11 11 0 141.82
app/models/o_embed_cache.rb 100.00 % 50 24 24 0 2.71
app/models/open_graph_cache.rb 97.30 % 58 37 36 1 4.22
app/models/participation.rb 100.00 % 40 22 22 0 31.91
app/models/person.rb 96.28 % 402 188 181 7 620.60
app/models/photo.rb 91.36 % 150 81 74 7 179.85
app/models/pod.rb 100.00 % 173 74 74 0 408.88
app/models/poll.rb 95.24 % 44 21 20 1 32.95
app/models/poll_answer.rb 100.00 % 13 7 7 0 1.00
app/models/poll_participation.rb 100.00 % 46 26 26 0 15.50
app/models/poll_participation_signature.rb 100.00 % 8 4 4 0 1.00
app/models/post.rb 98.73 % 169 79 78 1 114.58
app/models/profile.rb 97.92 % 172 96 94 2 2392.24
app/models/reference.rb 100.00 % 39 21 21 0 159.43
app/models/report.rb 85.71 % 63 35 30 5 5.37
app/models/reshare.rb 97.78 % 91 45 44 1 56.76
app/models/role.rb 88.46 % 53 26 23 3 42.31
app/models/service.rb 100.00 % 58 22 22 0 7.45
app/models/services/tumblr.rb 100.00 % 78 43 43 0 2.16
app/models/services/twitter.rb 82.76 % 113 58 48 10 3.53
app/models/services/wordpress.rb 100.00 % 33 15 15 0 1.13
app/models/share_visibility.rb 86.96 % 57 23 20 3 336.61
app/models/signature_order.rb 100.00 % 5 2 2 0 1.00
app/models/status_message.rb 98.48 % 136 66 65 1 334.33
app/models/tag_following.rb 100.00 % 13 6 6 0 1.50
app/models/user.rb 0.00 % 617 472 0 472 0.00
app/models/user/connecting.rb 0.00 % 65 43 0 43 0.00
app/models/user/querying.rb 0.00 % 179 132 0 132 0.00
app/models/user/social_actions.rb 0.00 % 69 58 0 58 0.00
app/models/user_preference.rb 100.00 % 28 7 7 0 10.71
app/presenters/aspect_membership_presenter.rb 100.00 % 14 5 5 0 35.40
app/presenters/aspect_presenter.rb 100.00 % 26 11 11 0 60.91
app/presenters/avatar_presenter.rb 100.00 % 32 12 12 0 85.58
app/presenters/base_presenter.rb 100.00 % 42 21 21 0 602.10
app/presenters/block_presenter.rb 100.00 % 8 3 3 0 1.00
app/presenters/comment_presenter.rb 94.44 % 63 18 17 1 3.50
app/presenters/contact_presenter.rb 100.00 % 30 13 13 0 30.31
app/presenters/conversation_presenter.rb 100.00 % 14 5 5 0 45.00
app/presenters/last_three_comments_decorator.rb 100.00 % 18 7 7 0 18.71
app/presenters/likes_presenter.rb 100.00 % 10 3 3 0 3.33
app/presenters/message_presenter.rb 100.00 % 12 3 3 0 2.00
app/presenters/node_info_presenter.rb 100.00 % 120 62 62 0 9.50
app/presenters/notification_presenter.rb 100.00 % 40 19 19 0 12.95
app/presenters/o_embed_presenter.rb 100.00 % 49 19 19 0 2.37
app/presenters/person_presenter.rb 97.40 % 175 77 75 2 50.10
app/presenters/photo_presenter.rb 88.89 % 40 9 8 1 18.67
app/presenters/pod_presenter.rb 100.00 % 23 4 4 0 3.75
app/presenters/poll_presenter.rb 100.00 % 35 13 13 0 10.62
app/presenters/post_interaction_presenter.rb 100.00 % 43 15 15 0 21.87
app/presenters/post_presenter.rb 97.70 % 239 87 85 2 38.39
app/presenters/profile_presenter.rb 100.00 % 73 22 22 0 16.18
app/presenters/service_presenter.rb 85.71 % 17 7 6 1 2.00
app/presenters/tag_stream_presenter.rb 100.00 % 27 10 10 0 5.10
app/presenters/user_application_presenter.rb 92.31 % 49 26 24 2 16.19
app/presenters/user_applications_presenter.rb 100.00 % 22 11 11 0 1.09
app/presenters/user_presenter.rb 100.00 % 55 24 24 0 162.75
app/serializers/export/aspect_serializer.rb 100.00 % 7 3 3 0 1.00
app/serializers/export/contact_serializer.rb 100.00 % 36 14 14 0 7.43
app/serializers/export/others_data_serializer.rb 100.00 % 35 16 16 0 21.63
app/serializers/export/own_post_serializer.rb 100.00 % 35 14 14 0 7.79
app/serializers/export/own_relayables_serializer.rb 100.00 % 15 6 6 0 4.00
app/serializers/export/user_serializer.rb 100.00 % 79 38 38 0 27.61
app/serializers/federation_entity_serializer.rb 92.86 % 33 14 13 1 12.43
app/serializers/flat_map_array_serializer.rb 100.00 % 11 5 5 0 53.40
app/serializers/notification_serializer.rb 100.00 % 22 8 8 0 4.00
app/serializers/serializer_post_processing.rb 100.00 % 22 6 6 0 14.67
app/serializers/user_info_serializer.rb 100.00 % 26 13 13 0 1.77
app/services/aspects_membership_service.rb 100.00 % 66 35 35 0 12.83
app/services/block_service.rb 100.00 % 29 16 16 0 4.69
app/services/comment_service.rb 96.77 % 56 31 30 1 46.94
app/services/conversation_service.rb 100.00 % 67 24 24 0 17.25
app/services/diaspora_link_service.rb 100.00 % 57 29 29 0 5.21
app/services/import_service.rb 79.10 % 110 67 53 14 1.40
app/services/like_service.rb 100.00 % 74 44 44 0 15.95
app/services/migration_service.rb 97.53 % 153 81 79 2 3.21
app/services/notification_service.rb 100.00 % 64 26 26 0 301.62
app/services/photo_service.rb 97.30 % 64 37 36 1 11.59
app/services/poll_participation_service.rb 100.00 % 18 9 9 0 7.00
app/services/post_service.rb 96.00 % 111 50 48 2 86.06
app/services/reshare_service.rb 100.00 % 26 14 14 0 22.29
app/services/status_message_creation_service.rb 100.00 % 92 51 51 0 46.80
app/services/tag_following_service.rb 91.67 % 42 24 22 2 6.29
app/uploaders/exported_photos.rb 0.00 % 19 11 0 11 0.00
app/uploaders/exported_user.rb 0.00 % 19 11 0 11 0.00
app/uploaders/processed_image.rb 100.00 % 34 16 16 0 685.56
app/uploaders/secure_uploader.rb 0.00 % 14 10 0 10 0.00
app/uploaders/unprocessed_image.rb 100.00 % 57 29 29 0 1478.31
app/workers/archive_base.rb 93.75 % 43 16 15 1 2.25
app/workers/base.rb 100.00 % 15 6 6 0 1.00
app/workers/check_birthday.rb 100.00 % 18 8 8 0 10.50
app/workers/clean_cached_files.rb 0.00 % 11 8 0 8 0.00
app/workers/cleanup_old_exports.rb 100.00 % 21 12 12 0 1.50
app/workers/cleanup_pending_photos.rb 100.00 % 11 5 5 0 1.40
app/workers/deferred_dispatch.rb 100.00 % 19 7 7 0 993.00
app/workers/deferred_retraction.rb 100.00 % 19 8 8 0 3.50
app/workers/delete_account.rb 100.00 % 17 6 6 0 1.33
app/workers/delete_post_from_service.rb 100.00 % 16 6 6 0 1.00
app/workers/export_photos.rb 100.00 % 22 9 9 0 1.78
app/workers/export_user.rb 100.00 % 22 9 9 0 3.11
app/workers/fetch_profile_photo.rb 100.00 % 31 13 13 0 2.69
app/workers/fetch_public_posts.rb 80.00 % 15 5 4 1 0.80
app/workers/fetch_webfinger.rb 100.00 % 20 6 6 0 1.17
app/workers/gather_o_embed_data.rb 100.00 % 24 8 8 0 3.38
app/workers/gather_open_graph_data.rb 100.00 % 24 8 8 0 3.38
app/workers/import_user.rb 0.00 % 12 9 0 9 0.00
app/workers/mail/also_commented.rb 100.00 % 9 3 3 0 1.00
app/workers/mail/comment_on_post.rb 100.00 % 9 3 3 0 1.00
app/workers/mail/confirm_email.rb 100.00 % 8 3 3 0 1.00
app/workers/mail/contacts_birthday.rb 100.00 % 8 3 3 0 1.00
app/workers/mail/csrf_token_fail.rb 100.00 % 8 3 3 0 1.00
app/workers/mail/invite_email.rb 100.00 % 17 6 6 0 1.00
app/workers/mail/liked.rb 100.00 % 14 7 7 0 2.00
app/workers/mail/liked_comment.rb 0.00 % 8 6 0 6 0.00
app/workers/mail/mentioned.rb 100.00 % 13 3 3 0 1.00
app/workers/mail/mentioned_in_comment.rb 100.00 % 8 3 3 0 1.00
app/workers/mail/notifier_base.rb 100.00 % 13 6 6 0 109.67
app/workers/mail/private_message.rb 100.00 % 13 3 3 0 1.00
app/workers/mail/report_worker.rb 83.33 % 13 6 5 1 0.83
app/workers/mail/reshared.rb 100.00 % 9 3 3 0 1.00
app/workers/mail/started_sharing.rb 100.00 % 14 3 3 0 1.00
app/workers/post_to_service.rb 100.00 % 17 7 7 0 1.00
app/workers/process_photo.rb 100.00 % 24 9 9 0 270.67
app/workers/publish_to_hub.rb 100.00 % 15 5 5 0 80.20
app/workers/queue_users_for_removal.rb 100.00 % 39 15 15 0 3.13
app/workers/receive_base.rb 100.00 % 33 7 7 0 29.14
app/workers/receive_local.rb 100.00 % 16 7 7 0 876.57
app/workers/receive_private.rb 100.00 % 17 7 7 0 30.14
app/workers/receive_public.rb 100.00 % 15 5 5 0 35.40
app/workers/recheck_scheduled_pods.rb 100.00 % 11 5 5 0 1.00
app/workers/recurring_pod_check.rb 100.00 % 11 5 5 0 1.00
app/workers/remove_old_user.rb 100.00 % 29 11 11 0 1.91
app/workers/reset_password.rb 100.00 % 11 5 5 0 1.20
app/workers/send_base.rb 100.00 % 31 14 14 0 3.43
app/workers/send_private.rb 100.00 % 15 7 7 0 5.00
app/workers/send_public.rb 100.00 % 15 7 7 0 2.29
lib/account_deleter.rb 0.00 % 102 64 0 64 0.00
lib/api/openid_connect/authorization_point/endpoint.rb 94.87 % 70 39 37 2 24.67
lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb 100.00 % 70 39 39 0 6.85
lib/api/openid_connect/authorization_point/endpoint_start_point.rb 94.44 % 31 18 17 1 8.56
lib/api/openid_connect/error.rb 77.78 % 18 9 7 2 0.78
lib/api/openid_connect/id_token.rb 100.00 % 72 22 22 0 10.82
lib/api/openid_connect/id_token_config.rb 72.73 % 18 11 8 3 0.73
lib/api/openid_connect/protected_resource_endpoint.rb 100.00 % 44 11 11 0 194.64
lib/api/openid_connect/subject_identifier_creator.rb 100.00 % 19 9 9 0 7.78
lib/api/openid_connect/token_endpoint.rb 96.77 % 60 31 30 1 7.23
lib/api/paging/index_paginator.rb 87.50 % 42 24 21 3 27.88
lib/api/paging/rest_paged_response_builder.rb 96.30 % 50 27 26 1 50.93
lib/api/paging/rest_paginator_builder.rb 95.45 % 82 44 42 2 27.48
lib/api/paging/time_paginator.rb 96.30 % 109 54 52 2 37.98
lib/archive_importer.rb 98.72 % 149 78 77 1 6.69
lib/archive_importer/archive_helper.rb 100.00 % 49 24 24 0 10.00
lib/archive_importer/block_importer.rb 93.75 % 30 16 15 1 1.13
lib/archive_importer/contact_importer.rb 100.00 % 43 24 24 0 3.42
lib/archive_importer/entity_importer.rb 100.00 % 33 16 16 0 11.63
lib/archive_importer/own_entity_importer.rb 100.00 % 33 17 17 0 12.59
lib/archive_importer/own_relayable_importer.rb 100.00 % 29 15 15 0 7.33
lib/archive_importer/post_importer.rb 100.00 % 35 17 17 0 8.12
lib/archive_validator.rb 100.00 % 60 25 25 0 6.40
lib/archive_validator/author_private_key_validator.rb 100.00 % 16 7 7 0 2.29
lib/archive_validator/base_validator.rb 100.00 % 27 14 14 0 45.00
lib/archive_validator/collection_validator.rb 100.00 % 16 7 7 0 32.43
lib/archive_validator/contact_validator.rb 100.00 % 41 22 22 0 8.68
lib/archive_validator/contacts_validator.rb 100.00 % 13 6 6 0 3.83
lib/archive_validator/entities_helper.rb 100.00 % 35 17 17 0 33.59
lib/archive_validator/others_relayables_validator.rb 100.00 % 13 6 6 0 2.83
lib/archive_validator/own_relayable_validator.rb 100.00 % 19 9 9 0 6.00
lib/archive_validator/post_validator.rb 100.00 % 22 11 11 0 9.09
lib/archive_validator/posts_validator.rb 100.00 % 13 6 6 0 6.33
lib/archive_validator/relayable_validator.rb 100.00 % 61 29 29 0 16.03
lib/archive_validator/relayables_validator.rb 100.00 % 13 6 6 0 4.33
lib/archive_validator/schema_validator.rb 100.00 % 13 6 6 0 2.00
lib/bookmarklet_renderer.rb 90.00 % 38 20 18 2 10.05
lib/configuration_methods.rb 0.00 % 161 123 0 123 0.00
lib/connection_tester.rb 87.27 % 273 110 96 14 5.78
lib/diaspora.rb 0.00 % 17 11 0 11 0.00
lib/diaspora/camo.rb 0.00 % 43 35 0 35 0.00
lib/diaspora/commentable.rb 91.67 % 33 12 11 1 61.58
lib/diaspora/entity_finder.rb 100.00 % 22 11 11 0 12.82
lib/diaspora/exceptions.rb 0.00 % 21 8 0 8 0.00
lib/diaspora/exporter.rb 0.00 % 27 17 0 17 0.00
lib/diaspora/exporter/others_relayables.rb 100.00 % 44 13 13 0 9.77
lib/diaspora/federated.rb 0.00 % 13 7 0 7 0.00
lib/diaspora/federated/base.rb 0.00 % 25 12 0 12 0.00
lib/diaspora/federated/contact_retraction.rb 0.00 % 20 15 0 15 0.00
lib/diaspora/federated/fetchable.rb 100.00 % 21 11 11 0 8.91
lib/diaspora/federated/generator.rb 94.44 % 33 18 17 1 485.28
lib/diaspora/federated/retraction.rb 0.00 % 73 54 0 54 0.00
lib/diaspora/federation.rb 0.00 % 22 14 0 14 0.00
lib/diaspora/federation/dispatcher.rb 0.00 % 92 72 0 72 0.00
lib/diaspora/federation/dispatcher/private.rb 0.00 % 27 21 0 21 0.00
lib/diaspora/federation/dispatcher/public.rb 0.00 % 35 27 0 27 0.00
lib/diaspora/federation/entities.rb 0.00 % 230 208 0 208 0.00
lib/diaspora/federation/mappings.rb 0.00 % 85 74 0 74 0.00
lib/diaspora/federation/receive.rb 0.00 % 381 320 0 320 0.00
lib/diaspora/fetcher.rb 0.00 % 7 5 0 5 0.00
lib/diaspora/fetcher/public.rb 0.00 % 165 102 0 102 0.00
lib/diaspora/fields/author.rb 100.00 % 17 8 8 0 4.50
lib/diaspora/fields/guid.rb 0.00 % 20 15 0 15 0.00
lib/diaspora/fields/target.rb 100.00 % 15 7 7 0 1.43
lib/diaspora/likeable.rb 100.00 % 22 8 8 0 227.63
lib/diaspora/logging.rb 0.00 % 12 8 0 8 0.00
lib/diaspora/markdownify.rb 0.00 % 8 6 0 6 0.00
lib/diaspora/markdownify/email.rb 0.00 % 11 9 0 9 0.00
lib/diaspora/markdownify/html.rb 0.00 % 17 14 0 14 0.00
lib/diaspora/mentionable.rb 0.00 % 131 61 0 61 0.00
lib/diaspora/mentions_container.rb 100.00 % 40 20 20 0 1094.15
lib/diaspora/message_renderer.rb 0.00 % 283 200 0 200 0.00
lib/diaspora/relayable.rb 96.30 % 57 27 26 1 525.70
lib/diaspora/shareable.rb 100.00 % 98 40 40 0 217.03
lib/diaspora/signature.rb 100.00 % 20 10 10 0 3.70
lib/diaspora/taggable.rb 100.00 % 73 34 34 0 3300.26
lib/direction_detector.rb 0.00 % 27 17 0 17 0.00
lib/email_inviter.rb 0.00 % 32 24 0 24 0.00
lib/error_page_renderer.rb 0.00 % 19 14 0 14 0.00
lib/evil_query.rb 0.00 % 124 99 0 99 0.00
lib/i18n_interpolation_fallbacks.rb 0.00 % 26 21 0 21 0.00
lib/node_info.rb 100.00 % 203 77 77 0 39.60
lib/photo_exporter.rb 95.83 % 46 24 23 1 4.67
lib/publisher.rb 0.00 % 12 9 0 9 0.00
lib/pubsubhubbub.rb 0.00 % 21 12 0 12 0.00
lib/share_visibility_converter.rb 0.00 % 55 47 0 47 0.00
lib/sidekiq_middlewares.rb 0.00 % 16 14 0 14 0.00
lib/stream.rb 0.00 % 15 13 0 13 0.00
lib/stream/activity.rb 0.00 % 20 14 0 14 0.00
lib/stream/aspect.rb 0.00 % 88 46 0 46 0.00
lib/stream/base.rb 0.00 % 102 67 0 67 0.00
lib/stream/comments.rb 0.00 % 20 11 0 11 0.00
lib/stream/followed_tag.rb 0.00 % 35 21 0 21 0.00
lib/stream/likes.rb 0.00 % 20 11 0 11 0.00
lib/stream/local_public.rb 0.00 % 23 14 0 14 0.00
lib/stream/mention.rb 0.00 % 20 11 0 11 0.00
lib/stream/multi.rb 0.00 % 80 54 0 54 0.00
lib/stream/person.rb 0.00 % 27 15 0 15 0.00
lib/stream/public.rb 0.00 % 25 14 0 14 0.00
lib/stream/tag.rb 0.00 % 58 40 0 40 0.00

Controllers ( 91.34% covered at 13.94 hits/line )

65 files in total.
2528 relevant lines, 2309 lines covered and 219 lines missed. ( 91.34% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/controllers/admin/admin_controller.rb 100.00 % 8 4 4 0 1.00
app/controllers/admin/pods_controller.rb 100.00 % 36 22 22 0 2.14
app/controllers/admin/users_controller.rb 30.91 % 92 55 17 38 0.31
app/controllers/admins_controller.rb 87.50 % 152 80 70 10 3.08
app/controllers/api/openid_connect/authorizations_controller.rb 91.88 % 262 160 147 13 9.94
app/controllers/api/openid_connect/clients_controller.rb 82.14 % 56 28 23 5 1.50
app/controllers/api/openid_connect/discovery_controller.rb 100.00 % 52 5 5 0 1.40
app/controllers/api/openid_connect/id_tokens_controller.rb 100.00 % 38 8 8 0 1.00
app/controllers/api/openid_connect/token_endpoint_controller.rb 95.12 % 65 41 39 2 6.80
app/controllers/api/openid_connect/user_applications_controller.rb 100.00 % 13 6 6 0 1.00
app/controllers/api/openid_connect/user_info_controller.rb 100.00 % 28 15 15 0 3.07
app/controllers/api/v1/aspects_controller.rb 97.73 % 79 44 43 1 2.41
app/controllers/api/v1/base_controller.rb 79.07 % 77 43 34 9 62.65
app/controllers/api/v1/comments_controller.rb 100.00 % 118 67 67 0 4.84
app/controllers/api/v1/contacts_controller.rb 100.00 % 62 34 34 0 2.88
app/controllers/api/v1/conversations_controller.rb 100.00 % 87 50 50 0 14.56
app/controllers/api/v1/likes_controller.rb 100.00 % 138 72 72 0 5.08
app/controllers/api/v1/messages_controller.rb 100.00 % 43 25 25 0 2.12
app/controllers/api/v1/notifications_controller.rb 96.67 % 57 30 29 1 3.37
app/controllers/api/v1/photos_controller.rb 97.62 % 76 42 41 1 3.45
app/controllers/api/v1/post_interactions_controller.rb 98.08 % 101 52 51 1 6.23
app/controllers/api/v1/posts_controller.rb 100.00 % 115 63 63 0 7.30
app/controllers/api/v1/reshares_controller.rb 96.00 % 52 25 24 1 2.28
app/controllers/api/v1/search_controller.rb 100.00 % 116 65 65 0 8.14
app/controllers/api/v1/streams_controller.rb 97.50 % 74 40 39 1 8.28
app/controllers/api/v1/tag_followings_controller.rb 100.00 % 41 21 21 0 2.14
app/controllers/api/v1/users_controller.rb 96.47 % 145 85 82 3 2.88
app/controllers/application_controller.rb 100.00 % 165 82 82 0 243.41
app/controllers/aspect_memberships_controller.rb 83.33 % 65 30 25 5 1.60
app/controllers/aspects_controller.rb 93.62 % 94 47 44 3 3.02
app/controllers/blocks_controller.rb 100.00 % 42 20 20 0 3.85
app/controllers/comments_controller.rb 93.18 % 86 44 41 3 2.70
app/controllers/contacts_controller.rb 88.89 % 107 54 48 6 5.33
app/controllers/conversation_visibilities_controller.rb 100.00 % 26 11 11 0 3.09
app/controllers/conversations_controller.rb 95.24 % 121 63 60 3 9.19
app/controllers/help_controller.rb 100.00 % 4 1 1 0 1.00
app/controllers/home_controller.rb 90.91 % 46 22 20 2 1.59
app/controllers/invitation_codes_controller.rb 100.00 % 25 13 13 0 1.23
app/controllers/invitations_controller.rb 100.00 % 78 42 42 0 5.57
app/controllers/likes_controller.rb 92.59 % 59 27 25 2 2.44
app/controllers/links_controller.rb 100.00 % 16 8 8 0 3.25
app/controllers/manifest_controller.rb 0.00 % 27 25 0 25 0.00
app/controllers/messages_controller.rb 100.00 % 28 13 13 0 4.54
app/controllers/node_info_controller.rb 92.86 % 33 14 13 1 3.43
app/controllers/notifications_controller.rb 100.00 % 98 50 50 0 11.92
app/controllers/participations_controller.rb 100.00 % 25 14 14 0 1.86
app/controllers/people_controller.rb 100.00 % 168 88 88 0 12.05
app/controllers/photos_controller.rb 80.22 % 177 91 73 18 4.56
app/controllers/poll_participations_controller.rb 100.00 % 24 13 13 0 2.08
app/controllers/posts_controller.rb 100.00 % 76 42 42 0 4.98
app/controllers/profiles_controller.rb 95.12 % 84 41 39 2 5.49
app/controllers/registrations_controller.rb 96.43 % 57 28 27 1 6.75
app/controllers/report_controller.rb 86.36 % 44 22 19 3 1.77
app/controllers/reshares_controller.rb 100.00 % 26 12 12 0 2.67
app/controllers/search_controller.rb 100.00 % 28 14 14 0 2.57
app/controllers/services_controller.rb 90.20 % 100 51 46 5 2.67
app/controllers/sessions_controller.rb 41.67 % 52 24 10 14 0.67
app/controllers/share_visibilities_controller.rb 100.00 % 22 9 9 0 1.89
app/controllers/status_messages_controller.rb 88.00 % 111 50 44 6 5.72
app/controllers/streams_controller.rb 88.37 % 87 43 38 5 6.28
app/controllers/tag_followings_controller.rb 87.50 % 55 24 21 3 2.04
app/controllers/tags_controller.rb 100.00 % 76 41 41 0 3.95
app/controllers/terms_controller.rb 85.71 % 21 7 6 1 1.29
app/controllers/two_factor_authentications_controller.rb 100.00 % 55 32 32 0 1.16
app/controllers/users_controller.rb 81.34 % 246 134 109 25 5.76

Channels ( 100.0% covered at 0.0 hits/line )

0 files in total.
0 relevant lines, 0 lines covered and 0 lines missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line

Models ( 60.14% covered at 191.35 hits/line )

65 files in total.
2584 relevant lines, 1554 lines covered and 1030 lines missed. ( 60.14% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/models/account_deletion.rb 100.00 % 33 15 15 0 2.93
app/models/account_migration.rb 100.00 % 278 133 133 0 128.70
app/models/api/openid_connect/authorization.rb 0.00 % 112 92 0 92 0.00
app/models/api/openid_connect/o_auth_access_token.rb 100.00 % 50 13 13 0 455.15
app/models/api/openid_connect/o_auth_application.rb 0.00 % 128 88 0 88 0.00
app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb 100.00 % 46 12 12 0 1.67
app/models/application_record.rb 0.00 % 5 3 0 3 0.00
app/models/aspect.rb 95.00 % 45 20 19 1 208.55
app/models/aspect_membership.rb 81.82 % 27 11 9 2 13.55
app/models/aspect_visibility.rb 100.00 % 14 4 4 0 1.00
app/models/block.rb 100.00 % 23 11 11 0 8.64
app/models/comment.rb 100.00 % 83 46 46 0 104.50
app/models/comment_signature.rb 100.00 % 8 4 4 0 1.00
app/models/contact.rb 95.56 % 104 45 43 2 250.51
app/models/conversation.rb 100.00 % 75 42 42 0 43.57
app/models/conversation_visibility.rb 100.00 % 18 9 9 0 8.33
app/models/invitation_code.rb 95.65 % 44 23 22 1 14.83
app/models/like.rb 100.00 % 48 26 26 0 42.81
app/models/like_signature.rb 100.00 % 8 4 4 0 1.00
app/models/location.rb 100.00 % 16 8 8 0 3.38
app/models/mention.rb 0.00 % 20 11 0 11 0.00
app/models/message.rb 100.00 % 44 24 24 0 15.54
app/models/notification.rb 0.00 % 58 40 0 40 0.00
app/models/notification_actor.rb 100.00 % 10 3 3 0 1.00
app/models/notifications/also_commented.rb 0.00 % 27 20 0 20 0.00
app/models/notifications/comment_on_post.rb 92.31 % 25 13 12 1 8.54
app/models/notifications/commented.rb 0.00 % 17 13 0 13 0.00
app/models/notifications/contacts_birthday.rb 100.00 % 19 10 10 0 1.80
app/models/notifications/liked.rb 100.00 % 26 13 13 0 14.46
app/models/notifications/liked_comment.rb 69.23 % 26 13 9 4 1.15
app/models/notifications/mentioned.rb 100.00 % 29 13 13 0 273.15
app/models/notifications/mentioned_in_comment.rb 94.74 % 35 19 18 1 4.26
app/models/notifications/mentioned_in_post.rb 91.67 % 24 12 11 1 140.75
app/models/notifications/private_message.rb 93.33 % 30 15 14 1 2.93
app/models/notifications/reshared.rb 83.33 % 24 12 10 2 3.33
app/models/notifications/started_sharing.rb 100.00 % 22 11 11 0 141.82
app/models/o_embed_cache.rb 100.00 % 50 24 24 0 2.71
app/models/open_graph_cache.rb 97.30 % 58 37 36 1 4.22
app/models/participation.rb 100.00 % 40 22 22 0 31.91
app/models/person.rb 96.28 % 402 188 181 7 620.60
app/models/photo.rb 91.36 % 150 81 74 7 179.85
app/models/pod.rb 100.00 % 173 74 74 0 408.88
app/models/poll.rb 95.24 % 44 21 20 1 32.95
app/models/poll_answer.rb 100.00 % 13 7 7 0 1.00
app/models/poll_participation.rb 100.00 % 46 26 26 0 15.50
app/models/poll_participation_signature.rb 100.00 % 8 4 4 0 1.00
app/models/post.rb 98.73 % 169 79 78 1 114.58
app/models/profile.rb 97.92 % 172 96 94 2 2392.24
app/models/reference.rb 100.00 % 39 21 21 0 159.43
app/models/report.rb 85.71 % 63 35 30 5 5.37
app/models/reshare.rb 97.78 % 91 45 44 1 56.76
app/models/role.rb 88.46 % 53 26 23 3 42.31
app/models/service.rb 100.00 % 58 22 22 0 7.45
app/models/services/tumblr.rb 100.00 % 78 43 43 0 2.16
app/models/services/twitter.rb 82.76 % 113 58 48 10 3.53
app/models/services/wordpress.rb 100.00 % 33 15 15 0 1.13
app/models/share_visibility.rb 86.96 % 57 23 20 3 336.61
app/models/signature_order.rb 100.00 % 5 2 2 0 1.00
app/models/status_message.rb 98.48 % 136 66 65 1 334.33
app/models/tag_following.rb 100.00 % 13 6 6 0 1.50
app/models/user.rb 0.00 % 617 472 0 472 0.00
app/models/user/connecting.rb 0.00 % 65 43 0 43 0.00
app/models/user/querying.rb 0.00 % 179 132 0 132 0.00
app/models/user/social_actions.rb 0.00 % 69 58 0 58 0.00
app/models/user_preference.rb 100.00 % 28 7 7 0 10.71

Mailers ( 99.51% covered at 110.28 hits/line )

19 files in total.
204 relevant lines, 203 lines covered and 1 lines missed. ( 99.51% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/mailers/application_mailer.rb 100.00 % 5 2 2 0 1.00
app/mailers/diaspora_devise_mailer.rb 75.00 % 9 4 3 1 0.75
app/mailers/export_mailer.rb 100.00 % 32 14 14 0 9.50
app/mailers/maintenance.rb 100.00 % 17 7 7 0 7.29
app/mailers/notification_mailers/also_commented.rb 100.00 % 25 13 13 0 7.85
app/mailers/notification_mailers/base.rb 100.00 % 68 34 34 0 518.24
app/mailers/notification_mailers/comment_on_post.rb 100.00 % 18 9 9 0 11.11
app/mailers/notification_mailers/confirm_email.rb 100.00 % 10 5 5 0 3.00
app/mailers/notification_mailers/contacts_birthday.rb 100.00 % 12 6 6 0 1.67
app/mailers/notification_mailers/csrf_token_fail.rb 100.00 % 9 4 4 0 2.00
app/mailers/notification_mailers/liked.rb 100.00 % 15 8 8 0 6.25
app/mailers/notification_mailers/liked_comment.rb 100.00 % 16 8 8 0 4.38
app/mailers/notification_mailers/mentioned.rb 100.00 % 15 8 8 0 41.13
app/mailers/notification_mailers/mentioned_in_comment.rb 100.00 % 14 7 7 0 9.14
app/mailers/notification_mailers/private_message.rb 100.00 % 16 9 9 0 4.33
app/mailers/notification_mailers/reshared.rb 100.00 % 16 8 8 0 2.88
app/mailers/notification_mailers/started_sharing.rb 100.00 % 9 4 4 0 129.25
app/mailers/notifier.rb 100.00 % 66 35 35 0 90.51
app/mailers/report_mailer.rb 100.00 % 35 19 19 0 12.05

Helpers ( 0.0% covered at 0.0 hits/line )

30 files in total.
815 relevant lines, 0 lines covered and 815 lines missed. ( 0.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/helpers/activity_streams_helper.rb 0.00 % 14 11 0 11 0.00
app/helpers/application_helper.rb 0.00 % 88 66 0 66 0.00
app/helpers/aspect_global_helper.rb 0.00 % 28 21 0 21 0.00
app/helpers/contacts_helper.rb 0.00 % 14 11 0 11 0.00
app/helpers/conversations_helper.rb 0.00 % 10 7 0 7 0.00
app/helpers/error_messages_helper.rb 0.00 % 28 19 0 19 0.00
app/helpers/getting_started_helper.rb 0.00 % 12 5 0 5 0.00
app/helpers/gon_helper.rb 0.00 % 10 8 0 8 0.00
app/helpers/interim_stream_hackiness_helper.rb 0.00 % 22 18 0 18 0.00
app/helpers/invitation_codes_helper.rb 0.00 % 21 17 0 17 0.00
app/helpers/language_helper.rb 0.00 % 31 24 0 24 0.00
app/helpers/layout_helper.rb 0.00 % 63 44 0 44 0.00
app/helpers/meta_data_helper.rb 0.00 % 55 42 0 42 0.00
app/helpers/mobile_helper.rb 0.00 % 95 83 0 83 0.00
app/helpers/notifications_helper.rb 0.00 % 114 97 0 97 0.00
app/helpers/notifier_helper.rb 0.00 % 24 14 0 14 0.00
app/helpers/o_embed_helper.rb 0.00 % 35 30 0 30 0.00
app/helpers/open_graph_helper.rb 0.00 % 11 8 0 8 0.00
app/helpers/people_helper.rb 0.00 % 77 59 0 59 0.00
app/helpers/posts_helper.rb 0.00 % 28 21 0 21 0.00
app/helpers/profile_helper.rb 0.00 % 17 10 0 10 0.00
app/helpers/publisher_helper.rb 0.00 % 49 36 0 36 0.00
app/helpers/report_helper.rb 0.00 % 25 18 0 18 0.00
app/helpers/services_helper.rb 0.00 % 13 9 0 9 0.00
app/helpers/sessions_helper.rb 0.00 % 20 15 0 15 0.00
app/helpers/statistics_helper.rb 0.00 % 39 30 0 30 0.00
app/helpers/stream_helper.rb 0.00 % 58 47 0 47 0.00
app/helpers/tags_helper.rb 0.00 % 18 14 0 14 0.00
app/helpers/user_applications_helper.rb 0.00 % 11 9 0 9 0.00
app/helpers/users_helper.rb 0.00 % 46 22 0 22 0.00

Jobs ( 92.02% covered at 54.47 hits/line )

48 files in total.
326 relevant lines, 300 lines covered and 26 lines missed. ( 92.02% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/workers/archive_base.rb 93.75 % 43 16 15 1 2.25
app/workers/base.rb 100.00 % 15 6 6 0 1.00
app/workers/check_birthday.rb 100.00 % 18 8 8 0 10.50
app/workers/clean_cached_files.rb 0.00 % 11 8 0 8 0.00
app/workers/cleanup_old_exports.rb 100.00 % 21 12 12 0 1.50
app/workers/cleanup_pending_photos.rb 100.00 % 11 5 5 0 1.40
app/workers/deferred_dispatch.rb 100.00 % 19 7 7 0 993.00
app/workers/deferred_retraction.rb 100.00 % 19 8 8 0 3.50
app/workers/delete_account.rb 100.00 % 17 6 6 0 1.33
app/workers/delete_post_from_service.rb 100.00 % 16 6 6 0 1.00
app/workers/export_photos.rb 100.00 % 22 9 9 0 1.78
app/workers/export_user.rb 100.00 % 22 9 9 0 3.11
app/workers/fetch_profile_photo.rb 100.00 % 31 13 13 0 2.69
app/workers/fetch_public_posts.rb 80.00 % 15 5 4 1 0.80
app/workers/fetch_webfinger.rb 100.00 % 20 6 6 0 1.17
app/workers/gather_o_embed_data.rb 100.00 % 24 8 8 0 3.38
app/workers/gather_open_graph_data.rb 100.00 % 24 8 8 0 3.38
app/workers/import_user.rb 0.00 % 12 9 0 9 0.00
app/workers/mail/also_commented.rb 100.00 % 9 3 3 0 1.00
app/workers/mail/comment_on_post.rb 100.00 % 9 3 3 0 1.00
app/workers/mail/confirm_email.rb 100.00 % 8 3 3 0 1.00
app/workers/mail/contacts_birthday.rb 100.00 % 8 3 3 0 1.00
app/workers/mail/csrf_token_fail.rb 100.00 % 8 3 3 0 1.00
app/workers/mail/invite_email.rb 100.00 % 17 6 6 0 1.00
app/workers/mail/liked.rb 100.00 % 14 7 7 0 2.00
app/workers/mail/liked_comment.rb 0.00 % 8 6 0 6 0.00
app/workers/mail/mentioned.rb 100.00 % 13 3 3 0 1.00
app/workers/mail/mentioned_in_comment.rb 100.00 % 8 3 3 0 1.00
app/workers/mail/notifier_base.rb 100.00 % 13 6 6 0 109.67
app/workers/mail/private_message.rb 100.00 % 13 3 3 0 1.00
app/workers/mail/report_worker.rb 83.33 % 13 6 5 1 0.83
app/workers/mail/reshared.rb 100.00 % 9 3 3 0 1.00
app/workers/mail/started_sharing.rb 100.00 % 14 3 3 0 1.00
app/workers/post_to_service.rb 100.00 % 17 7 7 0 1.00
app/workers/process_photo.rb 100.00 % 24 9 9 0 270.67
app/workers/publish_to_hub.rb 100.00 % 15 5 5 0 80.20
app/workers/queue_users_for_removal.rb 100.00 % 39 15 15 0 3.13
app/workers/receive_base.rb 100.00 % 33 7 7 0 29.14
app/workers/receive_local.rb 100.00 % 16 7 7 0 876.57
app/workers/receive_private.rb 100.00 % 17 7 7 0 30.14
app/workers/receive_public.rb 100.00 % 15 5 5 0 35.40
app/workers/recheck_scheduled_pods.rb 100.00 % 11 5 5 0 1.00
app/workers/recurring_pod_check.rb 100.00 % 11 5 5 0 1.00
app/workers/remove_old_user.rb 100.00 % 29 11 11 0 1.91
app/workers/reset_password.rb 100.00 % 11 5 5 0 1.20
app/workers/send_base.rb 100.00 % 31 14 14 0 3.43
app/workers/send_private.rb 100.00 % 15 7 7 0 5.00
app/workers/send_public.rb 100.00 % 15 7 7 0 2.29

Libraries ( 34.61% covered at 57.16 hits/line )

100 files in total.
3256 relevant lines, 1127 lines covered and 2129 lines missed. ( 34.61% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
lib/account_deleter.rb 0.00 % 102 64 0 64 0.00
lib/api/openid_connect/authorization_point/endpoint.rb 94.87 % 70 39 37 2 24.67
lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb 100.00 % 70 39 39 0 6.85
lib/api/openid_connect/authorization_point/endpoint_start_point.rb 94.44 % 31 18 17 1 8.56
lib/api/openid_connect/error.rb 77.78 % 18 9 7 2 0.78
lib/api/openid_connect/id_token.rb 100.00 % 72 22 22 0 10.82
lib/api/openid_connect/id_token_config.rb 72.73 % 18 11 8 3 0.73
lib/api/openid_connect/protected_resource_endpoint.rb 100.00 % 44 11 11 0 194.64
lib/api/openid_connect/subject_identifier_creator.rb 100.00 % 19 9 9 0 7.78
lib/api/openid_connect/token_endpoint.rb 96.77 % 60 31 30 1 7.23
lib/api/paging/index_paginator.rb 87.50 % 42 24 21 3 27.88
lib/api/paging/rest_paged_response_builder.rb 96.30 % 50 27 26 1 50.93
lib/api/paging/rest_paginator_builder.rb 95.45 % 82 44 42 2 27.48
lib/api/paging/time_paginator.rb 96.30 % 109 54 52 2 37.98
lib/archive_importer.rb 98.72 % 149 78 77 1 6.69
lib/archive_importer/archive_helper.rb 100.00 % 49 24 24 0 10.00
lib/archive_importer/block_importer.rb 93.75 % 30 16 15 1 1.13
lib/archive_importer/contact_importer.rb 100.00 % 43 24 24 0 3.42
lib/archive_importer/entity_importer.rb 100.00 % 33 16 16 0 11.63
lib/archive_importer/own_entity_importer.rb 100.00 % 33 17 17 0 12.59
lib/archive_importer/own_relayable_importer.rb 100.00 % 29 15 15 0 7.33
lib/archive_importer/post_importer.rb 100.00 % 35 17 17 0 8.12
lib/archive_validator.rb 100.00 % 60 25 25 0 6.40
lib/archive_validator/author_private_key_validator.rb 100.00 % 16 7 7 0 2.29
lib/archive_validator/base_validator.rb 100.00 % 27 14 14 0 45.00
lib/archive_validator/collection_validator.rb 100.00 % 16 7 7 0 32.43
lib/archive_validator/contact_validator.rb 100.00 % 41 22 22 0 8.68
lib/archive_validator/contacts_validator.rb 100.00 % 13 6 6 0 3.83
lib/archive_validator/entities_helper.rb 100.00 % 35 17 17 0 33.59
lib/archive_validator/others_relayables_validator.rb 100.00 % 13 6 6 0 2.83
lib/archive_validator/own_relayable_validator.rb 100.00 % 19 9 9 0 6.00
lib/archive_validator/post_validator.rb 100.00 % 22 11 11 0 9.09
lib/archive_validator/posts_validator.rb 100.00 % 13 6 6 0 6.33
lib/archive_validator/relayable_validator.rb 100.00 % 61 29 29 0 16.03
lib/archive_validator/relayables_validator.rb 100.00 % 13 6 6 0 4.33
lib/archive_validator/schema_validator.rb 100.00 % 13 6 6 0 2.00
lib/bookmarklet_renderer.rb 90.00 % 38 20 18 2 10.05
lib/configuration_methods.rb 0.00 % 161 123 0 123 0.00
lib/connection_tester.rb 87.27 % 273 110 96 14 5.78
lib/diaspora.rb 0.00 % 17 11 0 11 0.00
lib/diaspora/camo.rb 0.00 % 43 35 0 35 0.00
lib/diaspora/commentable.rb 91.67 % 33 12 11 1 61.58
lib/diaspora/entity_finder.rb 100.00 % 22 11 11 0 12.82
lib/diaspora/exceptions.rb 0.00 % 21 8 0 8 0.00
lib/diaspora/exporter.rb 0.00 % 27 17 0 17 0.00
lib/diaspora/exporter/others_relayables.rb 100.00 % 44 13 13 0 9.77
lib/diaspora/federated.rb 0.00 % 13 7 0 7 0.00
lib/diaspora/federated/base.rb 0.00 % 25 12 0 12 0.00
lib/diaspora/federated/contact_retraction.rb 0.00 % 20 15 0 15 0.00
lib/diaspora/federated/fetchable.rb 100.00 % 21 11 11 0 8.91
lib/diaspora/federated/generator.rb 94.44 % 33 18 17 1 485.28
lib/diaspora/federated/retraction.rb 0.00 % 73 54 0 54 0.00
lib/diaspora/federation.rb 0.00 % 22 14 0 14 0.00
lib/diaspora/federation/dispatcher.rb 0.00 % 92 72 0 72 0.00
lib/diaspora/federation/dispatcher/private.rb 0.00 % 27 21 0 21 0.00
lib/diaspora/federation/dispatcher/public.rb 0.00 % 35 27 0 27 0.00
lib/diaspora/federation/entities.rb 0.00 % 230 208 0 208 0.00
lib/diaspora/federation/mappings.rb 0.00 % 85 74 0 74 0.00
lib/diaspora/federation/receive.rb 0.00 % 381 320 0 320 0.00
lib/diaspora/fetcher.rb 0.00 % 7 5 0 5 0.00
lib/diaspora/fetcher/public.rb 0.00 % 165 102 0 102 0.00
lib/diaspora/fields/author.rb 100.00 % 17 8 8 0 4.50
lib/diaspora/fields/guid.rb 0.00 % 20 15 0 15 0.00
lib/diaspora/fields/target.rb 100.00 % 15 7 7 0 1.43
lib/diaspora/likeable.rb 100.00 % 22 8 8 0 227.63
lib/diaspora/logging.rb 0.00 % 12 8 0 8 0.00
lib/diaspora/markdownify.rb 0.00 % 8 6 0 6 0.00
lib/diaspora/markdownify/email.rb 0.00 % 11 9 0 9 0.00
lib/diaspora/markdownify/html.rb 0.00 % 17 14 0 14 0.00
lib/diaspora/mentionable.rb 0.00 % 131 61 0 61 0.00
lib/diaspora/mentions_container.rb 100.00 % 40 20 20 0 1094.15
lib/diaspora/message_renderer.rb 0.00 % 283 200 0 200 0.00
lib/diaspora/relayable.rb 96.30 % 57 27 26 1 525.70
lib/diaspora/shareable.rb 100.00 % 98 40 40 0 217.03
lib/diaspora/signature.rb 100.00 % 20 10 10 0 3.70
lib/diaspora/taggable.rb 100.00 % 73 34 34 0 3300.26
lib/direction_detector.rb 0.00 % 27 17 0 17 0.00
lib/email_inviter.rb 0.00 % 32 24 0 24 0.00
lib/error_page_renderer.rb 0.00 % 19 14 0 14 0.00
lib/evil_query.rb 0.00 % 124 99 0 99 0.00
lib/i18n_interpolation_fallbacks.rb 0.00 % 26 21 0 21 0.00
lib/node_info.rb 100.00 % 203 77 77 0 39.60
lib/photo_exporter.rb 95.83 % 46 24 23 1 4.67
lib/publisher.rb 0.00 % 12 9 0 9 0.00
lib/pubsubhubbub.rb 0.00 % 21 12 0 12 0.00
lib/share_visibility_converter.rb 0.00 % 55 47 0 47 0.00
lib/sidekiq_middlewares.rb 0.00 % 16 14 0 14 0.00
lib/stream.rb 0.00 % 15 13 0 13 0.00
lib/stream/activity.rb 0.00 % 20 14 0 14 0.00
lib/stream/aspect.rb 0.00 % 88 46 0 46 0.00
lib/stream/base.rb 0.00 % 102 67 0 67 0.00
lib/stream/comments.rb 0.00 % 20 11 0 11 0.00
lib/stream/followed_tag.rb 0.00 % 35 21 0 21 0.00
lib/stream/likes.rb 0.00 % 20 11 0 11 0.00
lib/stream/local_public.rb 0.00 % 23 14 0 14 0.00
lib/stream/mention.rb 0.00 % 20 11 0 11 0.00
lib/stream/multi.rb 0.00 % 80 54 0 54 0.00
lib/stream/person.rb 0.00 % 27 15 0 15 0.00
lib/stream/public.rb 0.00 % 25 14 0 14 0.00
lib/stream/tag.rb 0.00 % 58 40 0 40 0.00

Ungrouped ( 94.91% covered at 82.66 hits/line )

57 files in total.
1258 relevant lines, 1194 lines covered and 64 lines missed. ( 94.91% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line
app/presenters/aspect_membership_presenter.rb 100.00 % 14 5 5 0 35.40
app/presenters/aspect_presenter.rb 100.00 % 26 11 11 0 60.91
app/presenters/avatar_presenter.rb 100.00 % 32 12 12 0 85.58
app/presenters/base_presenter.rb 100.00 % 42 21 21 0 602.10
app/presenters/block_presenter.rb 100.00 % 8 3 3 0 1.00
app/presenters/comment_presenter.rb 94.44 % 63 18 17 1 3.50
app/presenters/contact_presenter.rb 100.00 % 30 13 13 0 30.31
app/presenters/conversation_presenter.rb 100.00 % 14 5 5 0 45.00
app/presenters/last_three_comments_decorator.rb 100.00 % 18 7 7 0 18.71
app/presenters/likes_presenter.rb 100.00 % 10 3 3 0 3.33
app/presenters/message_presenter.rb 100.00 % 12 3 3 0 2.00
app/presenters/node_info_presenter.rb 100.00 % 120 62 62 0 9.50
app/presenters/notification_presenter.rb 100.00 % 40 19 19 0 12.95
app/presenters/o_embed_presenter.rb 100.00 % 49 19 19 0 2.37
app/presenters/person_presenter.rb 97.40 % 175 77 75 2 50.10
app/presenters/photo_presenter.rb 88.89 % 40 9 8 1 18.67
app/presenters/pod_presenter.rb 100.00 % 23 4 4 0 3.75
app/presenters/poll_presenter.rb 100.00 % 35 13 13 0 10.62
app/presenters/post_interaction_presenter.rb 100.00 % 43 15 15 0 21.87
app/presenters/post_presenter.rb 97.70 % 239 87 85 2 38.39
app/presenters/profile_presenter.rb 100.00 % 73 22 22 0 16.18
app/presenters/service_presenter.rb 85.71 % 17 7 6 1 2.00
app/presenters/tag_stream_presenter.rb 100.00 % 27 10 10 0 5.10
app/presenters/user_application_presenter.rb 92.31 % 49 26 24 2 16.19
app/presenters/user_applications_presenter.rb 100.00 % 22 11 11 0 1.09
app/presenters/user_presenter.rb 100.00 % 55 24 24 0 162.75
app/serializers/export/aspect_serializer.rb 100.00 % 7 3 3 0 1.00
app/serializers/export/contact_serializer.rb 100.00 % 36 14 14 0 7.43
app/serializers/export/others_data_serializer.rb 100.00 % 35 16 16 0 21.63
app/serializers/export/own_post_serializer.rb 100.00 % 35 14 14 0 7.79
app/serializers/export/own_relayables_serializer.rb 100.00 % 15 6 6 0 4.00
app/serializers/export/user_serializer.rb 100.00 % 79 38 38 0 27.61
app/serializers/federation_entity_serializer.rb 92.86 % 33 14 13 1 12.43
app/serializers/flat_map_array_serializer.rb 100.00 % 11 5 5 0 53.40
app/serializers/notification_serializer.rb 100.00 % 22 8 8 0 4.00
app/serializers/serializer_post_processing.rb 100.00 % 22 6 6 0 14.67
app/serializers/user_info_serializer.rb 100.00 % 26 13 13 0 1.77
app/services/aspects_membership_service.rb 100.00 % 66 35 35 0 12.83
app/services/block_service.rb 100.00 % 29 16 16 0 4.69
app/services/comment_service.rb 96.77 % 56 31 30 1 46.94
app/services/conversation_service.rb 100.00 % 67 24 24 0 17.25
app/services/diaspora_link_service.rb 100.00 % 57 29 29 0 5.21
app/services/import_service.rb 79.10 % 110 67 53 14 1.40
app/services/like_service.rb 100.00 % 74 44 44 0 15.95
app/services/migration_service.rb 97.53 % 153 81 79 2 3.21
app/services/notification_service.rb 100.00 % 64 26 26 0 301.62
app/services/photo_service.rb 97.30 % 64 37 36 1 11.59
app/services/poll_participation_service.rb 100.00 % 18 9 9 0 7.00
app/services/post_service.rb 96.00 % 111 50 48 2 86.06
app/services/reshare_service.rb 100.00 % 26 14 14 0 22.29
app/services/status_message_creation_service.rb 100.00 % 92 51 51 0 46.80
app/services/tag_following_service.rb 91.67 % 42 24 22 2 6.29
app/uploaders/exported_photos.rb 0.00 % 19 11 0 11 0.00
app/uploaders/exported_user.rb 0.00 % 19 11 0 11 0.00
app/uploaders/processed_image.rb 100.00 % 34 16 16 0 685.56
app/uploaders/secure_uploader.rb 0.00 % 14 10 0 10 0.00
app/uploaders/unprocessed_image.rb 100.00 % 57 29 29 0 1478.31

app/controllers/admin/admin_controller.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Admin
  3. 1 class AdminController < ApplicationController
  4. 1 before_action :authenticate_user!
  5. 1 before_action :redirect_unless_admin
  6. end
  7. end

app/controllers/admin/pods_controller.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Admin
  3. 1 class PodsController < AdminController
  4. 1 respond_to :html, :json, :mobile
  5. 1 def index
  6. 3 pods_json = PodPresenter.as_collection(Pod.all)
  7. 3 respond_with do |format|
  8. 3 format.html do
  9. 2 gon.preloads[:pods] = pods_json
  10. 2 gon.unchecked_count = Pod.unchecked.count
  11. 2 gon.version_failed_count = Pod.version_failed.count
  12. 2 gon.error_count = Pod.check_failed.count
  13. 2 gon.active_count = Pod.active.count
  14. 2 gon.total_count = Pod.count
  15. 2 render "admins/pods"
  16. end
  17. 3 format.mobile { render "admins/pods" }
  18. 4 format.json { render json: pods_json }
  19. end
  20. end
  21. 1 def recheck
  22. 2 pod = Pod.find(params[:pod_id])
  23. 2 pod.test_connection!
  24. 2 respond_with do |format|
  25. 3 format.html { redirect_to admin_pods_path }
  26. 3 format.json { render json: PodPresenter.new(pod).as_json }
  27. end
  28. end
  29. end
  30. end

app/controllers/admin/users_controller.rb

30.91% lines covered

55 relevant lines. 17 lines covered and 38 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Admin
  3. 1 class UsersController < AdminController
  4. 1 before_action :validate_user, only: %i(make_admin remove_admin make_moderator remove_moderator make_spotlight remove_spotlight)
  5. 1 def close_account
  6. 1 u = User.find(params[:id])
  7. 1 u.close_account!
  8. 1 redirect_to user_search_path, notice: t("admins.user_search.account_closing_scheduled", name: u.username)
  9. end
  10. 1 def lock_account
  11. u = User.find(params[:id])
  12. u.lock_access!
  13. redirect_to user_search_path, notice: t("admins.user_search.account_locking_scheduled", name: u.username)
  14. end
  15. 1 def unlock_account
  16. u = User.find(params[:id])
  17. u.unlock_access!
  18. redirect_to user_search_path, notice: t("admins.user_search.account_unlocking_scheduled", name: u.username)
  19. end
  20. 1 def make_admin
  21. unless Role.is_admin? @user.person
  22. Role.add_admin @user.person
  23. notice = "admins.user_search.add_admin"
  24. else
  25. notice = "admins.user_search.role_implemented"
  26. end
  27. redirect_to user_search_path, notice: t(notice, name: @user.username)
  28. end
  29. 1 def remove_admin
  30. if Role.is_admin? @user.person
  31. Role.remove_admin @user.person
  32. notice = "admins.user_search.delete_admin"
  33. else
  34. notice = "admins.user_search.role_removal_implemented"
  35. end
  36. redirect_to user_search_path, notice: t(notice, name: @user.username)
  37. end
  38. 1 def make_moderator
  39. unless Role.moderator_only? @user.person
  40. Role.add_moderator @user.person
  41. notice = "admins.user_search.add_moderator"
  42. else
  43. notice = "admins.user_search.role_implemented"
  44. end
  45. redirect_to user_search_path, notice: t(notice, name: @user.username)
  46. end
  47. 1 def remove_moderator
  48. if Role.moderator_only? @user.person
  49. Role.remove_moderator @user.person
  50. notice = "admins.user_search.delete_moderator"
  51. else
  52. notice = "admins.user_search.role_removal_implemented"
  53. end
  54. redirect_to user_search_path, notice: t(notice, name: @user.username)
  55. end
  56. 1 def make_spotlight
  57. unless Role.spotlight? @user.person
  58. Role.add_spotlight @user.person
  59. notice = "admins.user_search.add_spotlight"
  60. else
  61. notice = "admins.user_search.role_implemented"
  62. end
  63. redirect_to user_search_path, notice: t(notice, name: @user.username)
  64. end
  65. 1 def remove_spotlight
  66. if Role.spotlight? @user.person
  67. Role.remove_spotlight @user.person
  68. notice = "admins.user_search.delete_spotlight"
  69. else
  70. notice = "admins.user_search.role_removal_implemented"
  71. end
  72. redirect_to user_search_path, notice: t(notice, name: @user.username)
  73. end
  74. 1 private
  75. 1 def validate_user
  76. @user = User.where(id: params[:id]).first
  77. redirect_to user_search_path, notice: t("admins.user_search.does_not_exist") unless @user
  78. end
  79. end
  80. end

app/controllers/admins_controller.rb

87.5% lines covered

80 relevant lines. 70 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class AdminsController < Admin::AdminController
  3. 1 include ApplicationHelper
  4. 1 def dashboard
  5. 4 gon.push(pod_version: pod_version)
  6. end
  7. 1 def user_search
  8. 5 if params[:admins_controller_user_search]
  9. 3 search_params = params.require(:admins_controller_user_search)
  10. .permit(:username, :email, :guid, :under13)
  11. 3 @search = UserSearch.new(search_params)
  12. 3 @users = @search.perform
  13. end
  14. 5 @search ||= UserSearch.new
  15. 5 @users ||= []
  16. end
  17. 1 def admin_inviter
  18. 4 inviter = InvitationCode.default_inviter_or(current_user)
  19. 4 email = params[:identifier]
  20. 4 user = User.find_by_email(email)
  21. 4 unless user
  22. 3 EmailInviter.new(email, inviter).send!
  23. 3 flash[:notice] = "invitation sent to #{email}"
  24. else
  25. 1 flash[:notice]= "error sending invite to #{email}"
  26. end
  27. 4 redirect_to user_search_path, :notice => flash[:notice]
  28. end
  29. 1 def add_invites
  30. InvitationCode.find_by_token(params[:invite_code_id]).add_invites!
  31. redirect_to user_search_path
  32. end
  33. 1 def weekly_user_stats
  34. @created_users_by_week = Hash.new{ |h,k| h[k] = [] }
  35. @created_users = User.where("username IS NOT NULL and created_at IS NOT NULL")
  36. @created_users.find_each do |u|
  37. week = u.created_at.beginning_of_week.strftime("%Y-%m-%d")
  38. @created_users_by_week[week] << {username: u.username, closed_account: u.person.closed_account}
  39. end
  40. @selected_week = params[:week] || @created_users_by_week.keys.last
  41. @counter = @created_users_by_week[@selected_week].count
  42. end
  43. 1 def stats
  44. 4 @popular_tags = ActsAsTaggableOn::Tagging.joins(:tag)
  45. .limit(50)
  46. .order(Arel.sql("count(taggings.id) DESC"))
  47. .group(:tag)
  48. .count
  49. 4 case params[:range]
  50. when "week"
  51. 1 range = 1.week
  52. 1 @segment = t('admins.stats.week')
  53. when "2weeks"
  54. 1 range = 2.weeks
  55. 1 @segment = t('admins.stats.2weeks')
  56. when "month"
  57. 1 range = 1.month
  58. 1 @segment = t('admins.stats.month')
  59. else
  60. 1 range = 1.day
  61. 1 @segment = t('admins.stats.daily')
  62. end
  63. 4 [Post, Comment, AspectMembership, User].each do |model|
  64. 16 create_hash(model, :range => range)
  65. end
  66. 4 @posts_per_day = Post.where("created_at >= ?", Time.zone.today - 21.days)
  67. .group(Arel.sql("DATE(created_at)"))
  68. .order(Arel.sql("DATE(created_at) ASC"))
  69. .count
  70. 4 @most_posts_within = @posts_per_day.values.max.to_f
  71. 4 @user_count = User.count
  72. #@posts[:new_public] = Post.where(:type => ['StatusMessage','ActivityStreams::Photo'],
  73. # :public => true).order('created_at DESC').limit(15).all
  74. end
  75. 1 private
  76. 1 def percent_change(today, yesterday)
  77. 16 sprintf( "%0.02f", ((today-yesterday) / yesterday.to_f)*100).to_f
  78. end
  79. 1 def create_hash(model, opts={})
  80. 16 opts[:range] ||= 1.day
  81. 16 plural = model.to_s.underscore.pluralize
  82. 16 eval(<<DATA
  83. @#{plural} = {
  84. :day_before => #{model}.where(:created_at => ((Time.now.midnight - #{opts[:range]*2})..Time.now.midnight - #{opts[:range]})).count,
  85. :yesterday => #{model}.where(:created_at => ((Time.now.midnight - #{opts[:range]})..Time.now.midnight)).count
  86. }
  87. @#{plural}[:change] = percent_change(@#{plural}[:yesterday], @#{plural}[:day_before])
  88. DATA
  89. )
  90. end
  91. 1 class UserSearch
  92. 1 include ActiveModel::Model
  93. 1 include ActiveModel::Conversion
  94. 1 include ActiveModel::Validations
  95. 1 attr_accessor :username, :email, :guid, :under13
  96. 1 validate :any_searchfield_present?
  97. 1 def initialize(attributes={})
  98. 5 assign_attributes(attributes)
  99. 5 yield(self) if block_given?
  100. end
  101. 1 def assign_attributes(values)
  102. 5 values.each do |k, v|
  103. 3 public_send("#{k}=", v)
  104. end
  105. end
  106. 1 def any_searchfield_present?
  107. 10 if %w(username email guid under13).all? { |attr| public_send(attr).blank? }
  108. errors.add :base, "no fields for search set"
  109. end
  110. end
  111. 1 def perform
  112. 3 return User.none unless valid?
  113. 3 users = User.arel_table
  114. 3 people = Person.arel_table
  115. 3 profiles = Profile.arel_table
  116. 3 res = User.joins(person: :profile)
  117. 3 res = res.where(users[:username].matches("%#{username}%")) unless username.blank?
  118. 3 res = res.where(users[:email].matches("%#{email}%")) unless email.blank?
  119. 3 res = res.where(people[:guid].matches("%#{guid}%")) unless guid.blank?
  120. 3 res = res.where(profiles[:birthday].gt(Date.today-13.years)) if under13 == '1'
  121. 3 res
  122. end
  123. end
  124. end

app/controllers/api/openid_connect/authorizations_controller.rb

91.88% lines covered

160 relevant lines. 147 lines covered and 13 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 class AuthorizationsController < ApplicationController
  5. 1 rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e|
  6. 3 logger.info e.backtrace[0, 10].join("\n")
  7. 3 error, _description = e.message.split(" :: ")
  8. 3 handle_params_error(error, "The request was malformed: please double check the client id and redirect uri.")
  9. end
  10. 1 rescue_from OpenSSL::SSL::SSLError do |e|
  11. logger.info e.backtrace[0, 10].join("\n")
  12. handle_params_error("bad_request", e.message)
  13. end
  14. 1 rescue_from JSON::JWS::VerificationFailed do |e|
  15. logger.info e.backtrace[0, 10].join("\n")
  16. handle_params_error("bad_request", e.message)
  17. end
  18. 1 before_action :auth_user_unless_prompt_none!
  19. 1 def new
  20. 33 auth = Api::OpenidConnect::Authorization.find_by_client_id_user_and_scopes(params[:client_id],
  21. current_user, params[:scope])
  22. 33 reset_auth(auth)
  23. 33 if logged_in_before?(params[:max_age])
  24. reauthenticate(params)
  25. 33 elsif params[:prompt]
  26. 2 prompt = params[:prompt].split(" ")
  27. 2 handle_prompt(prompt, auth)
  28. else
  29. 31 handle_authorization_form(auth)
  30. end
  31. end
  32. 1 def create
  33. 12 restore_request_parameters
  34. 12 process_authorization_consent(params[:approve])
  35. end
  36. 1 def destroy
  37. 2 authorization = Api::OpenidConnect::Authorization.find_by(id: params[:id])
  38. 2 if authorization
  39. 1 authorization.destroy
  40. else
  41. 1 flash[:error] = I18n.t("api.openid_connect.authorizations.destroy.fail", id: params[:id])
  42. end
  43. 2 redirect_to api_openid_connect_user_applications_url
  44. end
  45. 1 private
  46. 1 def reset_auth(auth)
  47. 33 return unless auth
  48. 3 auth.o_auth_access_tokens.destroy_all
  49. 3 auth.code_used = false
  50. 3 auth.save
  51. end
  52. 1 def handle_prompt(prompt, auth)
  53. 2 if prompt.include? "select_account"
  54. 1 handle_params_error("account_selection_required",
  55. "There is no support for choosing among multiple accounts")
  56. 1 elsif prompt.include? "consent"
  57. 1 request_authorization_consent_form
  58. else
  59. handle_authorization_form(auth)
  60. end
  61. end
  62. 1 def handle_authorization_form(auth)
  63. 31 if auth
  64. 2 process_authorization_consent("true")
  65. else
  66. 29 request_authorization_consent_form
  67. end
  68. end
  69. 1 def request_authorization_consent_form
  70. 30 add_claims_to_scopes
  71. 30 endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointStartPoint.new(current_user)
  72. 30 handle_start_point_response(endpoint)
  73. end
  74. 1 def add_claims_to_scopes
  75. 30 return unless params[:claims]
  76. 1 claims_json = JSON.parse(params[:claims])
  77. 1 return unless claims_json
  78. 1 claims_array = claims_json["userinfo"].try(:keys)
  79. 1 return unless claims_array
  80. 1 req = build_rack_request
  81. 1 claims = claims_array.unshift(req[:scope]).join(" ")
  82. 1 req.update_param("scope", claims)
  83. end
  84. 1 def logged_in_before?(seconds)
  85. 33 if seconds.nil?
  86. 33 false
  87. else
  88. (Time.now - current_user.current_sign_in_at) > seconds.to_i
  89. end
  90. end
  91. 1 def handle_start_point_response(endpoint)
  92. 30 status, header, _response = endpoint.call(request.env)
  93. 27 if status.in?([301, 302, 303, 307, 308])
  94. 4 redirect_to header["Location"]
  95. else
  96. 23 save_params_and_render_consent_form(endpoint)
  97. end
  98. end
  99. 1 def save_params_and_render_consent_form(endpoint)
  100. 23 @o_auth_application = endpoint.o_auth_application
  101. 23 @response_type = endpoint.response_type
  102. 23 @redirect_uri = endpoint.redirect_uri
  103. 23 @scopes = endpoint.scopes
  104. 23 save_request_parameters
  105. 23 @app = UserApplicationPresenter.new @o_auth_application, @scopes
  106. 23 override_content_security_policy_directives(form_action: %w[])
  107. 23 render :new
  108. end
  109. 1 def save_request_parameters
  110. 23 session[:client_id] = @o_auth_application.client_id
  111. 23 session[:response_type] = @response_type
  112. 23 session[:redirect_uri] = @redirect_uri
  113. 23 session[:scopes] = scopes_as_space_seperated_values
  114. 23 session[:state] = params[:state]
  115. 23 session[:nonce] = params[:nonce]
  116. end
  117. 1 def scopes_as_space_seperated_values
  118. 23 @scopes.join(" ")
  119. end
  120. 1 def process_authorization_consent(approved_string)
  121. 15 endpoint = Api::OpenidConnect::AuthorizationPoint::EndpointConfirmationPoint.new(
  122. current_user, to_boolean(approved_string))
  123. 15 handle_confirmation_endpoint_response(endpoint)
  124. end
  125. 1 def handle_confirmation_endpoint_response(endpoint)
  126. 15 _status, header, _response = endpoint.call(request.env)
  127. 15 delete_authorization_session_variables
  128. 15 redirect_to header["Location"]
  129. end
  130. 1 def delete_authorization_session_variables
  131. 15 session.delete(:client_id)
  132. 15 session.delete(:response_type)
  133. 15 session.delete(:redirect_uri)
  134. 15 session.delete(:scopes)
  135. 15 session.delete(:state)
  136. 15 session.delete(:nonce)
  137. end
  138. 1 def to_boolean(str)
  139. 15 str.downcase == "true"
  140. end
  141. 1 def restore_request_parameters
  142. 12 req = build_rack_request
  143. 12 req.update_param("client_id", session[:client_id])
  144. 12 req.update_param("redirect_uri", session[:redirect_uri])
  145. 12 req.update_param("response_type", response_type_as_space_seperated_values)
  146. 12 req.update_param("scope", session[:scopes])
  147. 12 req.update_param("state", session[:state])
  148. 12 req.update_param("nonce", session[:nonce])
  149. end
  150. 1 def build_rack_request
  151. 13 Rack::Request.new(request.env)
  152. end
  153. 1 def response_type_as_space_seperated_values
  154. 12 [*session[:response_type]].join(" ")
  155. end
  156. 1 def handle_params_error(error, error_description)
  157. 9 if params[:client_id] && params[:redirect_uri]
  158. 7 handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description)
  159. else
  160. 2 render_error I18n.t("api.openid_connect.error_page.could_not_authorize"), error_description
  161. end
  162. end
  163. 1 def handle_params_error_when_client_id_and_redirect_uri_exists(error, error_description)
  164. 7 app = Api::OpenidConnect::OAuthApplication.find_by(client_id: params[:client_id])
  165. 7 if app && app.redirect_uris.include?(params[:redirect_uri])
  166. 4 redirect_prompt_error_display(error, error_description)
  167. else
  168. 3 render_error I18n.t("api.openid_connect.error_page.could_not_authorize"),
  169. "Invalid client id or redirect uri"
  170. end
  171. end
  172. 1 def redirect_prompt_error_display(error, error_description)
  173. 4 redirect_params_hash = {error: error, error_description: error_description, state: params[:state]}
  174. 16 redirect_fragment = redirect_params_hash.compact.map {|key, value| key.to_s + "=" + value }.join("&")
  175. 4 redirect_to params[:redirect_uri] + "?" + redirect_fragment
  176. end
  177. 1 def auth_user_unless_prompt_none!
  178. 53 prompt = params[:prompt]
  179. 53 if prompt && prompt.include?("none")
  180. 6 handle_prompt_none
  181. 47 elsif prompt && prompt.include?("login")
  182. new_params = params.except("controller", "action").permit!.to_h.merge(prompt: prompt.remove("login"))
  183. reauthenticate(new_params)
  184. else
  185. 47 authenticate_user!
  186. end
  187. end
  188. 1 def handle_prompt_none
  189. 6 if params[:prompt] == "none"
  190. 5 if user_signed_in?
  191. 4 handle_prompt_with_signed_in_user
  192. else
  193. 1 handle_params_error("login_required", "User must already be logged in when `prompt` is `none`")
  194. end
  195. else
  196. 1 handle_params_error("invalid_request", "The 'none' value cannot be used with any other prompt value")
  197. end
  198. end
  199. 1 def handle_prompt_with_signed_in_user
  200. 4 client_id = params[:client_id]
  201. 4 if client_id
  202. 4 auth = Api::OpenidConnect::Authorization.find_by_client_id_user_and_scopes(client_id,
  203. current_user, params[:scope])
  204. 4 if auth
  205. 1 process_authorization_consent("true")
  206. else
  207. 3 handle_params_error("interaction_required", "User must already be authorized when `prompt` is `none`")
  208. end
  209. else
  210. handle_params_error("bad_request", "Client ID is missing from request")
  211. end
  212. end
  213. 1 def reauthenticate(params)
  214. sign_out current_user
  215. redirect_to new_api_openid_connect_authorization_path(params)
  216. end
  217. 1 def render_error(error_description, detailed_error=nil)
  218. 5 @error_description = error_description
  219. 5 @detailed_error = detailed_error
  220. 5 if request.format == :mobile
  221. render "api/openid_connect/error/error.mobile", layout: "application.mobile"
  222. else
  223. 5 render "api/openid_connect/error/error", layout: "with_header_with_footer"
  224. end
  225. end
  226. end
  227. end
  228. end

app/controllers/api/openid_connect/clients_controller.rb

82.14% lines covered

28 relevant lines. 23 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 class ClientsController < ApplicationController
  5. 1 skip_before_action :verify_authenticity_token
  6. 1 rescue_from OpenIDConnect::HttpError do |e|
  7. http_error_page_as_json(e)
  8. end
  9. 1 rescue_from OpenIDConnect::ValidationFailed,
  10. ActiveRecord::RecordInvalid, Api::OpenidConnect::Error::InvalidSectorIdentifierUri do |e|
  11. 1 validation_fail_as_json(e)
  12. end
  13. 1 rescue_from Api::OpenidConnect::Error::InvalidRedirectUri do |e|
  14. validation_fail_redirect_uri(e)
  15. end
  16. 1 rescue_from OpenSSL::SSL::SSLError do |e|
  17. validation_fail_as_json(e)
  18. end
  19. # Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/controllers/connect/clients_controller.rb#L24
  20. 1 def create
  21. 7 registrar = OpenIDConnect::Client::Registrar.new(request.url, params)
  22. 7 client = Api::OpenidConnect::OAuthApplication.register! registrar
  23. 6 render json: client.as_json(root: false)
  24. end
  25. 1 def find
  26. 2 client = Api::OpenidConnect::OAuthApplication.find_by(client_name: params[:client_name])
  27. 2 if client
  28. 1 render json: {client_id: client.client_id}
  29. else
  30. 1 render json: {error: "Client with name #{params[:client_name]} does not exist"}
  31. end
  32. end
  33. 1 private
  34. 1 def http_error_page_as_json(e)
  35. render json: {error: :invalid_request, error_description: e.message}, status: 400
  36. end
  37. 1 def validation_fail_as_json(e)
  38. 1 render json: {error: :invalid_client_metadata, error_description: e.message}, status: 400
  39. end
  40. 1 def validation_fail_redirect_uri(e)
  41. render json: {error: :invalid_redirect_uri, error_description: e.message}, status: 400
  42. end
  43. end
  44. end
  45. end

app/controllers/api/openid_connect/discovery_controller.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2011 nov matake
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. # See https://github.com/nov/openid_connect_sample/blob/master/app/controllers/discovery_controller.rb
  23. 1 module Api
  24. 1 module OpenidConnect
  25. 1 class DiscoveryController < ApplicationController
  26. 1 def configuration
  27. 3 render json: OpenIDConnect::Discovery::Provider::Config::Response.new(
  28. issuer: AppConfig.environment.url,
  29. registration_endpoint: api_openid_connect_clients_url,
  30. authorization_endpoint: new_api_openid_connect_authorization_url,
  31. token_endpoint: api_openid_connect_access_tokens_url,
  32. userinfo_endpoint: api_openid_connect_user_info_url,
  33. jwks_uri: api_openid_connect_url,
  34. scopes_supported: Api::OpenidConnect::Authorization::SCOPES,
  35. response_types_supported: Api::OpenidConnect::OAuthApplication.available_response_types,
  36. request_object_signing_alg_values_supported: %i(none),
  37. request_parameter_supported: true,
  38. request_uri_parameter_supported: true,
  39. subject_types_supported: %w(public pairwise),
  40. id_token_signing_alg_values_supported: %i(RS256),
  41. token_endpoint_auth_methods_supported: %w(client_secret_basic client_secret_post private_key_jwt),
  42. claims_parameter_supported: true,
  43. claims_supported: %w(sub name nickname profile picture),
  44. userinfo_signing_alg_values_supported: %w(none)
  45. )
  46. end
  47. end
  48. end
  49. end

app/controllers/api/openid_connect/id_tokens_controller.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2011 nov matake
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. 1 module Api
  23. 1 module OpenidConnect
  24. 1 class IdTokensController < ApplicationController
  25. 1 def jwks
  26. 1 render json: JSON::JWK::Set.new(build_jwk).as_json
  27. end
  28. 1 private
  29. 1 def build_jwk
  30. 1 JSON::JWK.new(Api::OpenidConnect::IdTokenConfig::PUBLIC_KEY, use: :sig, kid: :default)
  31. end
  32. end
  33. end
  34. end

app/controllers/api/openid_connect/token_endpoint_controller.rb

95.12% lines covered

41 relevant lines. 39 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 class TokenEndpointController < ApplicationController
  5. 1 skip_before_action :verify_authenticity_token
  6. 1 def create
  7. 27 req = Rack::Request.new(request.env)
  8. 27 if req["client_assertion_type"] == "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
  9. 7 handle_jwt_bearer(req)
  10. end
  11. 23 self.status, headers, self.response_body = Api::OpenidConnect::TokenEndpoint.new.call(request.env)
  12. 95 headers.each {|name, value| response.headers[name] = value }
  13. nil
  14. end
  15. 1 private
  16. 1 def handle_jwt_bearer(req)
  17. 7 jwt_string = req["client_assertion"]
  18. 7 jwt = JSON::JWT.decode jwt_string, :skip_verification
  19. 4 o_auth_app = Api::OpenidConnect::OAuthApplication.find_by(client_id: jwt["iss"])
  20. 4 raise Rack::OAuth2::Server::Authorize::BadRequest(:invalid_request) unless o_auth_app
  21. 4 public_key = fetch_public_key(o_auth_app, jwt)
  22. 4 JSON::JWT.decode(jwt_string, JSON::JWK.new(public_key).to_key)
  23. 3 req.update_param("client_id", o_auth_app.client_id)
  24. 3 req.update_param("client_secret", o_auth_app.client_secret)
  25. end
  26. 1 def fetch_public_key(o_auth_app, jwt)
  27. 4 public_key = fetch_public_key_from_json(o_auth_app.jwks, jwt)
  28. 4 if public_key.empty? && o_auth_app.jwks_uri
  29. response = Faraday.get(o_auth_app.jwks_uri)
  30. public_key = fetch_public_key_from_json(response.body, jwt)
  31. end
  32. 4 raise Rack::OAuth2::Server::Authorize::BadRequest(:unauthorized_client) if public_key.empty?
  33. 4 public_key
  34. end
  35. 1 def fetch_public_key_from_json(string, jwt)
  36. 4 json = JSON.parse(string)
  37. 4 keys = json["keys"]
  38. 4 public_key = get_key_from_kid(keys, jwt.header["kid"])
  39. 4 public_key
  40. end
  41. 1 def get_key_from_kid(keys, kid)
  42. 4 keys.each do |key|
  43. 8 return key if key.has_value?(kid)
  44. end
  45. end
  46. 1 rescue_from Rack::OAuth2::Server::Authorize::BadRequest,
  47. JSON::JWT::InvalidFormat, JSON::JWK::UnknownAlgorithm do |e|
  48. 3 logger.info e.backtrace[0, 10].join("\n")
  49. 3 render json: {error: :invalid_request, error_description: e.message, status: 400}
  50. end
  51. 1 rescue_from JSON::JWT::VerificationFailed do |e|
  52. 1 logger.info e.backtrace[0, 10].join("\n")
  53. 1 render json: {error: :invalid_grant, error_description: e.message, status: 400}
  54. end
  55. end
  56. end
  57. end

app/controllers/api/openid_connect/user_applications_controller.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 class UserApplicationsController < ApplicationController
  5. 1 before_action :authenticate_user!
  6. 1 def index
  7. 1 @user_apps = UserApplicationsPresenter.new current_user
  8. end
  9. end
  10. end
  11. end

app/controllers/api/openid_connect/user_info_controller.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 class UserInfoController < ApplicationController
  5. 1 include Api::OpenidConnect::ProtectedResourceEndpoint
  6. 1 before_action do
  7. 5 require_access_token %w(openid)
  8. end
  9. 1 def show
  10. 2 serializer = UserInfoSerializer.new(current_user)
  11. 2 auth = current_token.authorization
  12. 2 serializer.serialization_options = {authorization: auth}
  13. attributes_without_essential =
  14. 12 serializer.attributes.with_indifferent_access.select {|scope| auth.scopes.include? scope }
  15. 2 attributes = attributes_without_essential.merge(
  16. sub: serializer.sub)
  17. 2 render json: attributes.to_json
  18. end
  19. 1 def current_user
  20. 12 current_token ? current_token.authorization.user : nil
  21. end
  22. end
  23. end
  24. end

app/controllers/api/v1/aspects_controller.rb

97.73% lines covered

44 relevant lines. 43 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class AspectsController < Api::V1::BaseController
  5. 1 before_action except: %i[create update destroy] do
  6. 5 require_access_token %w[contacts:read]
  7. end
  8. 1 before_action only: %i[create update destroy] do
  9. 14 require_access_token %w[contacts:modify]
  10. end
  11. 1 def index
  12. 1 aspects_query = current_user.aspects
  13. 1 aspects_page = index_pager(aspects_query).response
  14. 3 aspects_page[:data] = aspects_page[:data].map {|a| aspect_as_json(a, false) }
  15. 1 render_paged_api_response aspects_page
  16. end
  17. 1 def show
  18. 2 aspect = current_user.aspects.where(id: params[:id]).first
  19. 2 if aspect
  20. 1 render json: aspect_as_json(aspect, true)
  21. else
  22. 1 render_error 404, "Aspect with provided ID could not be found"
  23. end
  24. end
  25. 1 def create
  26. 3 params.require(%i[name])
  27. 2 aspect = current_user.aspects.build(name: params[:name])
  28. 2 if aspect&.save
  29. 1 render json: aspect_as_json(aspect, true)
  30. else
  31. 1 render_error 422, "Failed to create the aspect"
  32. end
  33. rescue ActionController::ParameterMissing
  34. 1 render_error 422, "Failed to create the aspect"
  35. end
  36. 1 def update
  37. 6 aspect = current_user.aspects.where(id: params[:id]).first
  38. 6 if !aspect
  39. 1 render_error 404, "Failed to update the aspect"
  40. 5 elsif aspect.update!(aspect_params(true))
  41. 4 render json: aspect_as_json(aspect, true)
  42. else
  43. render_error 422, "Failed to update the aspect"
  44. end
  45. rescue ActionController::ParameterMissing, ActiveRecord::RecordInvalid
  46. 1 render_error 422, "Failed to update the aspect"
  47. end
  48. 1 def destroy
  49. 2 aspect = current_user.aspects.where(id: params[:id]).first
  50. 2 if aspect&.destroy
  51. 1 head :no_content
  52. else
  53. 1 render_error 422, "Failed to delete the aspect"
  54. end
  55. end
  56. 1 private
  57. 1 def aspect_params(allow_order=false)
  58. 5 parameters = params.permit(:name)
  59. 5 parameters[:order_id] = params[:order] if params.has_key?(:order) && allow_order
  60. 5 parameters
  61. end
  62. 1 def aspect_as_json(aspect, as_full)
  63. 8 AspectPresenter.new(aspect).as_api_json(as_full)
  64. end
  65. end
  66. end
  67. end

app/controllers/api/v1/base_controller.rb

79.07% lines covered

43 relevant lines. 34 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class BaseController < ApplicationController
  5. 1 include Api::OpenidConnect::ProtectedResourceEndpoint
  6. 404 protect_from_forgery unless: -> { request.format.json? }
  7. 1 protected
  8. 1 rescue_from Exception do |e|
  9. logger.error e.message
  10. logger.error e.backtrace.join("\n")
  11. render_error 500, e.message
  12. end
  13. 1 rescue_from Rack::OAuth2::Server::Resource::Bearer::Unauthorized do |e|
  14. logger.error e.message
  15. render_error 403, e.message
  16. end
  17. 1 rescue_from Rack::OAuth2::Server::Resource::Forbidden do |e|
  18. 47 logger.error e.message
  19. 47 render_error 403, e.message
  20. end
  21. 1 rescue_from ActiveRecord::RecordNotFound do |e|
  22. logger.error e.message
  23. render_error 404, "No record found for the given id"
  24. end
  25. 1 rescue_from ActiveRecord::RecordInvalid do |e|
  26. logger.error e.message
  27. render_error 422, e.message
  28. end
  29. 1 rescue_from ActionController::ParameterMissing do |e|
  30. 2 logger.error e.message
  31. 2 render_error 422, e.message.split("\n").first
  32. end
  33. 1 def current_user
  34. 1500 current_token ? current_token.authorization.user : nil
  35. end
  36. 1 def index_pager(query)
  37. 28 Api::Paging::RestPaginatorBuilder.new(query, request).index_pager(params)
  38. end
  39. 1 def render_paged_api_response(page)
  40. 75 link_header = []
  41. 75 link_header << %(<#{page[:links][:next]}>; rel="next") if page[:links][:next]
  42. 75 link_header << %(<#{page[:links][:previous]}>; rel="previous") if page[:links][:previous]
  43. 75 response.set_header("Link", link_header.join(", ")) if link_header.present?
  44. 75 render json: page[:data]
  45. end
  46. 1 def render_error(code, message)
  47. 191 render json: {code: code, message: message}, status: code
  48. end
  49. 1 def time_pager(query)
  50. 17 Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params)
  51. end
  52. 1 def private_read?
  53. 52 access_token? %w[private:read]
  54. end
  55. 1 def private_modify?
  56. 11 access_token? %w[private:modify]
  57. end
  58. end
  59. end
  60. end

app/controllers/api/v1/comments_controller.rb

100.0% lines covered

67 relevant lines. 67 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class CommentsController < Api::V1::BaseController
  5. 1 before_action except: %i[create destroy] do
  6. 13 require_access_token %w[public:read]
  7. end
  8. 1 before_action only: %i[create destroy] do
  9. 12 require_access_token %w[interactions public:read]
  10. end
  11. 1 rescue_from ActiveRecord::RecordNotFound do
  12. 7 render_error 404, "Post with provided guid could not be found"
  13. end
  14. 1 rescue_from ActiveRecord::RecordInvalid do
  15. 1 render_error 422, "User is not allowed to comment"
  16. end
  17. 1 def create
  18. 5 find_post
  19. 3 comment = comment_service.create(params.require(:post_id), params.require(:body))
  20. rescue ActiveRecord::RecordNotFound
  21. 2 render_error 404, "Post with provided guid could not be found"
  22. else
  23. 2 render json: comment_as_json(comment), status: :created
  24. end
  25. 1 def index
  26. 5 find_post
  27. 3 comments_query = comment_service.find_for_post(params.require(:post_id))
  28. 3 params[:after] = Time.utc(1900).iso8601 if params.permit(:before, :after).empty?
  29. 3 comments_page = time_pager(comments_query).response
  30. 9 comments_page[:data] = comments_page[:data].map {|x| comment_as_json(x) }
  31. 3 render_paged_api_response comments_page
  32. end
  33. 1 def destroy
  34. 6 find_post
  35. 4 if comment_and_post_validate(params.require(:post_id), params[:id])
  36. 2 comment_service.destroy!(params[:id])
  37. 1 head :no_content
  38. end
  39. rescue ActiveRecord::RecordInvalid
  40. 1 render_error 403, "User not allowed to delete the comment"
  41. end
  42. 1 def report
  43. 8 find_post
  44. 5 post_guid = params.require(:post_id)
  45. 5 comment_guid = params.require(:comment_id)
  46. 5 return unless comment_and_post_validate(post_guid, comment_guid)
  47. 3 reason = params.require(:reason)
  48. 3 comment = comment_service.find!(comment_guid)
  49. 3 report = current_user.reports.new(
  50. item_id: comment.id,
  51. item_type: "Comment",
  52. text: reason
  53. )
  54. 3 if report.save
  55. 2 head :no_content
  56. else
  57. 1 render_error 409, "This item already has been reported by this user"
  58. end
  59. end
  60. 1 private
  61. 1 def comment_and_post_validate(post_guid, comment_guid)
  62. 9 if !comment_exists(comment_guid)
  63. 2 render_error 404, "Comment not found for the given post"
  64. 2 false
  65. 7 elsif !comment_is_for_post(post_guid, comment_guid)
  66. 2 render_error 404, "Comment not found for the given post"
  67. 2 false
  68. else
  69. 5 true
  70. end
  71. end
  72. 1 def comment_is_for_post(post_guid, comment_guid)
  73. 7 comments = comment_service.find_for_post(post_guid)
  74. 15 comment = comments.find {|comment| comment[:guid] == comment_guid }
  75. 7 comment ? true : false
  76. end
  77. 1 def comment_exists(comment_guid)
  78. 9 comment = comment_service.find!(comment_guid)
  79. 7 comment ? true : false
  80. rescue ActiveRecord::RecordNotFound
  81. 2 false
  82. end
  83. 1 def comment_service
  84. 27 @comment_service ||= CommentService.new(current_user)
  85. end
  86. 1 def post_service
  87. 24 @post_service ||= PostService.new(current_user)
  88. end
  89. 1 def comment_as_json(comment)
  90. 8 CommentPresenter.new(comment, current_user).as_api_response
  91. end
  92. 1 def find_post
  93. 24 post = post_service.find!(params[:post_id])
  94. 19 return post if post.public? || private_read?
  95. 4 raise ActiveRecord::RecordNotFound
  96. end
  97. end
  98. end
  99. end

app/controllers/api/v1/contacts_controller.rb

100.0% lines covered

34 relevant lines. 34 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "api/paging/index_paginator"
  3. 1 module Api
  4. 1 module V1
  5. 1 class ContactsController < Api::V1::BaseController
  6. 1 before_action except: %i[create destroy] do
  7. 5 require_access_token %w[contacts:read]
  8. end
  9. 1 before_action only: %i[create destroy] do
  10. 12 require_access_token %w[contacts:modify]
  11. end
  12. 1 rescue_from ActiveRecord::RecordNotFound do
  13. 4 render_error 404, "Aspect with provided ID could not be found"
  14. end
  15. 1 def index
  16. 4 contacts_query = aspects_membership_service.contacts_in_aspect(params.require(:aspect_id))
  17. 2 contacts_page = index_pager(contacts_query).response
  18. 2 contacts_page[:data] = contacts_page[:data].map do |c|
  19. 2 ContactPresenter.new(c, current_user).as_api_json_without_contact
  20. end
  21. 2 render_paged_api_response contacts_page
  22. end
  23. 1 def create
  24. 5 aspect_id = params.require(:aspect_id)
  25. 5 person = Person.find_by(guid: params.require(:person_guid))
  26. 5 aspect_membership = aspects_membership_service.create(aspect_id, person.id) if person.present?
  27. 2 if aspect_membership
  28. 1 head :no_content
  29. else
  30. 1 render_error 422, "Failed to add user to aspect"
  31. end
  32. rescue ActiveRecord::RecordNotUnique
  33. 1 render_error 422, "Failed to add user to aspect"
  34. end
  35. 1 def destroy
  36. 5 aspect_id = params.require(:aspect_id)
  37. 5 person = Person.find_by(guid: params[:id])
  38. 5 result = aspects_membership_service.destroy_by_ids(aspect_id, person.id) if person.present?
  39. 2 if result && result[:success]
  40. 1 head :no_content
  41. else
  42. 1 render_error 422, "Failed to remove user from aspect"
  43. end
  44. rescue ActiveRecord::RecordNotFound
  45. 3 render_error 404, "Aspect or contact on aspect not found"
  46. end
  47. 1 def aspects_membership_service
  48. 12 AspectsMembershipService.new(current_user)
  49. end
  50. end
  51. end
  52. end

app/controllers/api/v1/conversations_controller.rb

100.0% lines covered

50 relevant lines. 50 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class ConversationsController < Api::V1::BaseController
  5. 1 include ConversationsHelper
  6. 1 BOOLEAN_TYPE = ActiveModel::Type::Boolean.new
  7. 1 before_action do
  8. 71 require_access_token %w[conversations]
  9. end
  10. 1 rescue_from ActiveRecord::RecordNotFound do
  11. 5 render_error 404, "Conversation with provided guid could not be found"
  12. end
  13. 1 def index
  14. 4 mapped_params = {}
  15. 4 mapped_params[:only_after] = params[:only_after] if params.has_key?(:only_after)
  16. 4 mapped_params[:unread] = BOOLEAN_TYPE.cast(params[:only_unread]) if params.has_key?(:only_unread)
  17. 4 conversations_query = conversation_service.all_for_user(mapped_params)
  18. 4 conversations_page = pager(conversations_query, "conversations.created_at").response
  19. 12 conversations_page[:data] = conversations_page[:data].map {|x| conversation_as_json(x) }
  20. 4 render_paged_api_response conversations_page
  21. end
  22. 1 def show
  23. 6 conversation = conversation_service.find!(params[:id])
  24. 2 render json: conversation_as_json(conversation)
  25. end
  26. 1 def create
  27. 49 params.require(%i[subject body recipients])
  28. 45 recipients = recipient_ids
  29. 44 conversation = conversation_service.build(params[:subject], params[:body], recipients)
  30. 44 raise ActiveRecord::RecordInvalid unless conversation_valid?(conversation, recipients)
  31. 43 conversation.save!
  32. 43 Diaspora::Federation::Dispatcher.defer_dispatch(current_user, conversation)
  33. 43 render json: conversation_as_json(conversation), status: :created
  34. rescue ActiveRecord::RecordInvalid, ActionController::ParameterMissing, ActiveRecord::RecordNotFound
  35. 6 render_error 422, "Couldn't accept or process the conversation"
  36. end
  37. 1 def update
  38. 3 read = BOOLEAN_TYPE.cast(params.require(:read))
  39. 2 conversation = conversation_service.find!(params[:id])
  40. 2 conversation.update_read_for(current_user, read: read)
  41. 2 render json: conversation_as_json(conversation)
  42. rescue ActiveRecord::RecordInvalid, ActionController::ParameterMissing, ActiveRecord::RecordNotFound
  43. 1 render_error 422, "Couldn't update the conversation"
  44. end
  45. 1 def destroy
  46. 4 conversation = conversation_service.get_visibility(params[:id])
  47. 3 conversation.destroy!
  48. 3 head :no_content
  49. end
  50. 1 private
  51. 1 def conversation_service
  52. 60 @conversation_service ||= ConversationService.new(current_user)
  53. end
  54. 1 def conversation_as_json(conversation)
  55. 55 ConversationPresenter.new(conversation, current_user).as_api_json
  56. end
  57. 1 def pager(query, sort_field)
  58. 4 Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, sort_field)
  59. end
  60. 1 def recipient_ids
  61. 90 params[:recipients].map {|p| Person.find_from_guid_or_username(id: p).id }
  62. end
  63. 1 def conversation_valid?(conversation, recipients)
  64. 44 conversation.participants.length == (recipients.length + 1)
  65. end
  66. end
  67. end
  68. end

app/controllers/api/v1/likes_controller.rb

100.0% lines covered

72 relevant lines. 72 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class LikesController < Api::V1::BaseController
  5. 1 before_action do
  6. 35 require_access_token %w[public:read]
  7. end
  8. 1 before_action only: %i[create destroy] do
  9. 26 require_access_token %w[interactions]
  10. end
  11. 1 rescue_from ActiveRecord::RecordNotFound do
  12. 8 render_error 404, "Post with provided guid could not be found"
  13. end
  14. 1 rescue_from ActiveRecord::RecordInvalid do
  15. 4 render_error 422, "User is not allowed to like"
  16. end
  17. 1 def show
  18. 9 post = post_service.find!(params.require(:post_id))
  19. 7 raise ActiveRecord::RecordInvalid unless post.public? || private_read?
  20. 5 likes_query = find_likes
  21. 5 return unless likes_query
  22. 4 likes_page = index_pager(likes_query).response
  23. 10 likes_page[:data] = likes_page[:data].map {|x| like_json(x) }
  24. 4 render_paged_api_response likes_page
  25. end
  26. 1 def create
  27. 11 post = post_service.find!(params.require(:post_id))
  28. 9 raise ActiveRecord::RecordInvalid unless post.public? || private_read?
  29. 7 if params[:comment_id].present?
  30. 4 create_for_comment
  31. else
  32. 3 create_for_post
  33. end
  34. rescue ActiveRecord::RecordInvalid => e
  35. 4 if e.message == "Validation failed: Target has already been taken"
  36. 2 return render_error 409, "Like already exists"
  37. end
  38. 2 raise
  39. end
  40. 1 def destroy
  41. 11 post = post_service.find!(params.require(:post_id))
  42. 7 raise ActiveRecord::RecordInvalid unless post.public? || private_read?
  43. 7 if params[:comment_id].present?
  44. 4 destroy_for_comment
  45. else
  46. 3 destroy_for_post
  47. end
  48. end
  49. 1 private
  50. 1 def find_likes
  51. 5 if params[:comment_id].present?
  52. 3 return unless comment_and_post_validate(params[:post_id], params[:comment_id])
  53. 2 like_service.find_for_comment(params[:comment_id])
  54. else
  55. 2 like_service.find_for_post(params[:post_id])
  56. end
  57. end
  58. 1 def like_service
  59. 16 @like_service ||= LikeService.new(current_user)
  60. end
  61. 1 def post_service
  62. 31 @post_service ||= PostService.new(current_user)
  63. end
  64. 1 def comment_service
  65. 11 @comment_service ||= CommentService.new(current_user)
  66. end
  67. 1 def like_json(like)
  68. 6 LikesPresenter.new(like).as_api_json
  69. end
  70. 1 def create_for_post
  71. 3 like_service.create_for_post(params[:post_id])
  72. 2 head :no_content
  73. end
  74. 1 def create_for_comment
  75. 4 return unless comment_and_post_validate(params[:post_id], params[:comment_id])
  76. 3 like_service.create_for_comment(params[:comment_id])
  77. 2 head :no_content
  78. end
  79. 1 def destroy_for_post
  80. 3 if like_service.unlike_post(params[:post_id])
  81. 2 head :no_content
  82. else
  83. 1 render_error 410, "Like doesn’t exist"
  84. end
  85. end
  86. 1 def destroy_for_comment
  87. 4 return unless comment_and_post_validate(params[:post_id], params[:comment_id])
  88. 3 if like_service.unlike_comment(params[:comment_id])
  89. 2 head :no_content
  90. else
  91. 1 render_error 410, "Like doesn’t exist"
  92. end
  93. end
  94. 1 def comment_and_post_validate(post_guid, comment_guid)
  95. 11 if comment_is_for_post(post_guid, comment_guid)
  96. 8 true
  97. else
  98. 3 render_error 404, "Comment not found for the given post"
  99. 3 false
  100. end
  101. end
  102. 1 def comment_is_for_post(post_guid, comment_guid)
  103. 11 comments = comment_service.find_for_post(post_guid)
  104. 11 comments.exists?(guid: comment_guid)
  105. end
  106. end
  107. end
  108. end

app/controllers/api/v1/messages_controller.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class MessagesController < Api::V1::BaseController
  5. 1 before_action do
  6. 8 require_access_token %w[conversations]
  7. end
  8. 1 rescue_from ActiveRecord::RecordNotFound do
  9. 1 render_error 404, "Conversation with provided guid could not be found"
  10. end
  11. 1 def create
  12. 4 conversation = conversation_service.find!(params.require(:conversation_id))
  13. 3 text = params.require(:body)
  14. 1 message = current_user.build_message(conversation, text: text)
  15. 1 message.save!
  16. 1 Diaspora::Federation::Dispatcher.defer_dispatch(current_user, message)
  17. 1 render json: message_json(message), status: :created
  18. rescue ActionController::ParameterMissing
  19. 2 render_error 422, "Couldn’t accept or process the conversation"
  20. end
  21. 1 def index
  22. 2 conversation = conversation_service.find!(params.require(:conversation_id))
  23. 2 messages_page = index_pager(conversation.messages).response
  24. 5 messages_page[:data] = messages_page[:data].map {|x| message_json(x) }
  25. 2 render_paged_api_response messages_page
  26. end
  27. 1 private
  28. 1 def conversation_service
  29. 6 ConversationService.new(current_user)
  30. end
  31. 1 def message_json(message)
  32. 4 MessagePresenter.new(message).as_api_json
  33. end
  34. end
  35. end
  36. end

app/controllers/api/v1/notifications_controller.rb

96.67% lines covered

30 relevant lines. 29 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class NotificationsController < Api::V1::BaseController
  5. 1 BOOLEAN_TYPE = ActiveModel::Type::Boolean.new
  6. 1 before_action do
  7. 17 require_access_token %w[notifications]
  8. end
  9. 1 rescue_from ActiveRecord::RecordNotFound do
  10. 1 render_error 404, "Notification with provided guid could not be found"
  11. end
  12. 1 def show
  13. 3 notification = service.get_by_guid(params[:id])
  14. 3 if notification
  15. 1 render json: NotificationPresenter.new(notification).as_api_json
  16. else
  17. 2 render_error 404, "Notification with provided guid could not be found"
  18. end
  19. end
  20. 1 def index
  21. 7 after_date = Date.iso8601(params[:only_after]) if params.has_key?(:only_after)
  22. 6 notifications_query = service.index(BOOLEAN_TYPE.cast(params[:only_unread]), after_date)
  23. 6 notifications_page = time_pager(notifications_query).response
  24. 6 notifications_page[:data] = notifications_page[:data].map do |note|
  25. 9 NotificationPresenter.new(note, default_serializer_options).as_api_json
  26. end
  27. 6 render_paged_api_response notifications_page
  28. rescue ArgumentError
  29. 1 render_error 422, "Could not process the notifications request"
  30. end
  31. 1 def update
  32. 4 read = BOOLEAN_TYPE.cast(params.require(:read))
  33. 3 if service.update_status_by_guid(params[:id], read)
  34. 2 head :no_content
  35. else
  36. render_error 422, "Could not process the notifications request"
  37. end
  38. rescue ActionController::ParameterMissing
  39. 1 render_error 422, "Could not process the notifications request"
  40. end
  41. 1 private
  42. 1 def service
  43. 12 @service ||= NotificationService.new(current_user)
  44. end
  45. end
  46. end
  47. end

app/controllers/api/v1/photos_controller.rb

97.62% lines covered

42 relevant lines. 41 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class PhotosController < Api::V1::BaseController
  5. 1 before_action except: %i[create destroy] do
  6. 8 require_access_token %w[public:read]
  7. end
  8. 1 before_action only: %i[create destroy] do
  9. 14 require_access_token %w[public:modify]
  10. end
  11. 1 rescue_from ActiveRecord::RecordNotFound do
  12. 5 render_error 404, "Photo with provided guid could not be found"
  13. end
  14. 1 def index
  15. 2 query = if private_read?
  16. 1 current_user.photos
  17. else
  18. 1 current_user.photos.where(public: true)
  19. end
  20. 2 photos_page = time_pager(query).response
  21. 5 photos_page[:data] = photos_page[:data].map {|photo| photo_json(photo) }
  22. 2 render_paged_api_response photos_page
  23. end
  24. 1 def show
  25. 6 photo = photo_service.visible_photo(params.require(:id))
  26. 6 raise ActiveRecord::RecordNotFound unless photo
  27. 4 raise ActiveRecord::RecordNotFound unless photo.public? || private_read?
  28. 3 render json: photo_json(photo)
  29. end
  30. 1 def create
  31. 7 image = params.require(:image)
  32. 6 public_photo = params.has_key?(:aspect_ids)
  33. 6 raise RuntimeError unless public_photo || private_modify?
  34. 6 base_params = params.permit(:aspect_ids, :pending, :set_profile_photo)
  35. 6 photo = photo_service.create_from_params_and_file(base_params, image)
  36. 4 raise RuntimeError unless photo
  37. 4 render json: photo_json(photo)
  38. rescue CarrierWave::IntegrityError, ActionController::ParameterMissing, RuntimeError
  39. 3 render_error 422, "Failed to create the photo"
  40. end
  41. 1 def destroy
  42. 3 photo = current_user.photos.where(guid: params[:id]).first
  43. 3 raise ActiveRecord::RecordNotFound unless photo
  44. 1 raise ActiveRecord::RecordNotFound unless photo.public? || private_modify?
  45. 1 if current_user.retract(photo)
  46. 1 head :no_content
  47. else
  48. render_error 422, "Not allowed to delete the photo"
  49. end
  50. end
  51. 1 private
  52. 1 def photo_service
  53. 12 @photo_service ||= PhotoService.new(current_user)
  54. end
  55. 1 def photo_json(photo)
  56. 10 PhotoPresenter.new(photo).as_api_json(true)
  57. end
  58. end
  59. end
  60. end

app/controllers/api/v1/post_interactions_controller.rb

98.08% lines covered

52 relevant lines. 51 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class PostInteractionsController < Api::V1::BaseController
  5. 1 include PostsHelper
  6. 1 before_action do
  7. 42 require_access_token %w[public:read interactions]
  8. end
  9. 1 rescue_from ActiveRecord::RecordNotFound do
  10. 10 render_error 404, "Post with provided guid could not be found"
  11. end
  12. 1 def subscribe
  13. 11 post = find_post
  14. 9 return head :conflict if current_user.participations.find_by(target_id: post.id)
  15. 8 current_user.participate!(post)
  16. 8 head :no_content
  17. rescue ActiveRecord::RecordInvalid
  18. render_error 422, "Cannot subscribe to this post"
  19. end
  20. 1 def hide
  21. 9 return render_error(422, "Missing parameter") if params[:hide].nil?
  22. 8 post = find_post
  23. 6 hidden = current_user.is_shareable_hidden?(post)
  24. 6 if (params[:hide] && !hidden) || (!params[:hide] && hidden)
  25. 4 current_user.toggle_hidden_shareable(post)
  26. 4 head :no_content
  27. else
  28. 2 render_error(params[:hide] ? 409 : 410, params[:hide] ? "Post already hidden" : "Post not hidden")
  29. end
  30. end
  31. 1 def mute
  32. 5 post = find_post
  33. 3 participation = current_user.participations.find_by(target_id: post.id)
  34. 3 return head :gone unless participation
  35. 2 participation.destroy
  36. 2 head :no_content
  37. end
  38. 1 def report
  39. 6 reason = params.require(:reason)
  40. 5 post = find_post
  41. 3 report = current_user.reports.new(
  42. item_id: post.id,
  43. item_type: "Post",
  44. text: reason
  45. )
  46. 3 if report.save
  47. 2 head :no_content
  48. else
  49. 1 render_error 409, "Failed to create report on this post"
  50. end
  51. rescue ActionController::ParameterMissing
  52. 1 render_error 422, "Failed to create report on this post"
  53. end
  54. 1 def vote
  55. 6 post = find_post
  56. begin
  57. 4 poll_vote = poll_service.vote(post.id, params[:poll_answer])
  58. rescue ActiveRecord::RecordNotFound
  59. # This, but not the find_post above, should return a 422,
  60. # we just keep poll_vote nil so it goes into the else below
  61. end
  62. 3 if poll_vote
  63. 2 head :no_content
  64. else
  65. 1 render_error 422, "Cant vote on this post"
  66. end
  67. rescue ActiveRecord::RecordInvalid
  68. 1 render_error 422, "Cant vote on this post"
  69. end
  70. 1 private
  71. 1 def post_service
  72. 35 @post_service ||= PostService.new(current_user)
  73. end
  74. 1 def poll_service
  75. 4 @poll_service ||= PollParticipationService.new(current_user)
  76. end
  77. 1 def find_post
  78. 35 post = post_service.find!(params[:post_id])
  79. 30 raise ActiveRecord::RecordNotFound unless post.public? || private_read?
  80. 25 post
  81. end
  82. end
  83. end
  84. end

app/controllers/api/v1/posts_controller.rb

100.0% lines covered

63 relevant lines. 63 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class PostsController < Api::V1::BaseController
  5. 1 include PostsHelper
  6. 1 before_action except: %i[create destroy] do
  7. 8 require_access_token %w[public:read]
  8. end
  9. 1 before_action only: %i[create destroy] do
  10. 29 require_access_token %w[public:modify]
  11. end
  12. 1 rescue_from ActiveRecord::RecordNotFound do
  13. 4 render_error 404, "Post with provided guid could not be found"
  14. end
  15. 1 def show
  16. 8 post = post_service.find!(params[:id])
  17. 6 raise ActiveRecord::RecordNotFound unless post.public? || private_read?
  18. 5 render json: post_as_json(post)
  19. end
  20. 1 def create
  21. 23 creation_params = normalized_create_params
  22. 13 raise StandardError unless creation_params[:public] || private_modify?
  23. 13 @status_message = creation_service.create(creation_params)
  24. 8 render json: PostPresenter.new(@status_message, current_user).as_api_response
  25. rescue StandardError
  26. 15 render_error 422, "Failed to create the post"
  27. end
  28. 1 def destroy
  29. 4 post_service.destroy(params[:id], private_modify?)
  30. 1 head :no_content
  31. rescue Diaspora::NotMine, Diaspora::NonPublic
  32. 2 render_error 403, "Not allowed to delete the post"
  33. end
  34. 1 private
  35. 1 def normalized_create_params
  36. mapped_parameters = {
  37. 23 status_message: {
  38. text: params[:body]
  39. },
  40. public: params.require(:public),
  41. aspect_ids: normalize_aspect_ids(params.permit(aspects: []))
  42. }
  43. 21 add_location_params(mapped_parameters)
  44. 21 add_poll_params(mapped_parameters)
  45. 19 add_photo_ids(mapped_parameters)
  46. 13 mapped_parameters
  47. end
  48. 1 def add_location_params(mapped_parameters)
  49. 21 return unless params.has_key?(:location)
  50. 1 location = params.require(:location)
  51. 1 mapped_parameters[:location_address] = location[:address]
  52. 1 mapped_parameters[:location_coords] = "#{location[:lat]},#{location[:lng]}"
  53. end
  54. 1 def add_photo_ids(mapped_parameters)
  55. 19 return unless params.has_key?(:photos)
  56. 8 photo_guids = params[:photos]
  57. 8 return if photo_guids.empty?
  58. 20 photos = photo_guids.map {|guid| Photo.find_by!(guid: guid) }
  59. 10 .select {|p| p.author_id == current_user.person.id && p.pending }
  60. 6 raise InvalidArgument if photos.length != photo_guids.length
  61. 2 mapped_parameters[:photos] = photos
  62. end
  63. 1 def add_poll_params(mapped_parameters)
  64. 21 return unless params.has_key?(:poll)
  65. 4 poll_data = params.require(:poll)
  66. 4 question = poll_data[:question]
  67. 4 answers = poll_data[:poll_answers]
  68. 4 raise InvalidArgument if question.blank?
  69. 4 raise InvalidArgument if answers.empty?
  70. 4 answers.each do |a|
  71. 8 raise InvalidArgument if a.blank?
  72. end
  73. 2 mapped_parameters[:poll_question] = question
  74. 2 mapped_parameters[:poll_answers] = answers
  75. end
  76. 1 def normalize_aspect_ids(aspects)
  77. 21 aspects.empty? ? [] : aspects[:aspects]
  78. end
  79. 1 def post_service
  80. 12 @post_service ||= PostService.new(current_user)
  81. end
  82. 1 def creation_service
  83. 13 @creation_service ||= StatusMessageCreationService.new(current_user)
  84. end
  85. 1 def post_as_json(post)
  86. 5 PostPresenter.new(post, current_user).as_api_response
  87. end
  88. end
  89. end
  90. end

app/controllers/api/v1/reshares_controller.rb

96.0% lines covered

25 relevant lines. 24 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class ResharesController < Api::V1::BaseController
  5. 1 before_action except: %i[create] do
  6. 5 require_access_token %w[public:read]
  7. end
  8. 1 before_action only: %i[create] do
  9. 6 require_access_token %w[public:modify]
  10. end
  11. 1 rescue_from ActiveRecord::RecordNotFound do
  12. 3 render_error 404, "Post with provided guid could not be found"
  13. end
  14. 1 rescue_from Diaspora::NonPublic do
  15. render_error 404, "Post with provided guid could not be found"
  16. end
  17. 1 def show
  18. 5 reshares_query = reshare_service.find_for_post(params.require(:post_id))
  19. 2 reshares_page = index_pager(reshares_query).response
  20. 2 reshares_page[:data] = reshares_page[:data].map do |r|
  21. {
  22. 1 guid: r.guid,
  23. created_at: r.created_at,
  24. author: PersonPresenter.new(r.author).as_api_json
  25. }
  26. end
  27. 2 render_paged_api_response reshares_page
  28. end
  29. 1 def create
  30. 5 reshare = reshare_service.create(params.require(:post_id))
  31. rescue ActiveRecord::RecordInvalid
  32. 1 render_error 409, "Reshare already exists"
  33. rescue ActiveRecord::RecordNotFound, RuntimeError
  34. 3 render_error 422, "Failed to reshare"
  35. else
  36. 1 render json: PostPresenter.new(reshare, current_user).as_api_response
  37. end
  38. 1 private
  39. 1 def reshare_service
  40. 10 @reshare_service ||= ReshareService.new(current_user)
  41. end
  42. end
  43. end
  44. end

app/controllers/api/v1/search_controller.rb

100.0% lines covered

65 relevant lines. 65 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class SearchController < Api::V1::BaseController
  5. 1 USER_FILTER_CONTACTS = "contacts"
  6. 1 USER_FILTER_RECEIVING_CONTACTS = "contacts:receiving"
  7. 1 USER_FILTER_SHARING_CONTACTS = "contacts:sharing"
  8. 1 USER_FILTER_ASPECTS_PREFIX = "aspect:"
  9. 1 USER_FILTERS_EXACT_MATCH = [USER_FILTER_CONTACTS, USER_FILTER_RECEIVING_CONTACTS,
  10. USER_FILTER_SHARING_CONTACTS].freeze
  11. 1 USER_FILTERS_PREFIX_MATCH = [USER_FILTER_ASPECTS_PREFIX].freeze
  12. 1 before_action do
  13. 30 require_access_token %w[public:read]
  14. end
  15. 1 rescue_from RuntimeError do |e|
  16. 6 render_error 422, e.message
  17. end
  18. 1 def user_index
  19. 21 user_page = index_pager(people_query).response
  20. 32 user_page[:data] = user_page[:data].map {|p| PersonPresenter.new(p).as_api_json }
  21. 13 render_paged_api_response user_page
  22. end
  23. 1 def post_index
  24. 6 posts_page = time_pager(posts_query, "posts.created_at", "created_at").response
  25. 14 posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post).as_api_response }
  26. 5 render_paged_api_response posts_page
  27. end
  28. 1 def tag_index
  29. 3 tags_page = index_pager(tags_query).response
  30. 2 tags_page[:data] = tags_page[:data].pluck(:name)
  31. 2 render_paged_api_response tags_page
  32. end
  33. 1 private
  34. 1 def time_pager(query, query_time_field, data_time_field)
  35. 5 Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, query_time_field, data_time_field)
  36. end
  37. 1 def people_query
  38. 21 tag = params[:tag]
  39. 21 name_or_handle = params[:name_or_handle]
  40. 21 raise "Parameters tag and name_or_handle are exclusive" if tag.present? && name_or_handle.present?
  41. 20 query = if tag.present?
  42. # scope filters to only searchable people already
  43. 1 Person.profile_tagged_with(tag)
  44. 19 elsif name_or_handle.present?
  45. 18 Person.searchable(contacts_read? && current_user) # rubocop:disable Rails/DynamicFindBy
  46. .find_by_substring(name_or_handle)
  47. else
  48. 1 raise "Missing parameter tag or name_or_handle"
  49. end
  50. 19 query = query.where(closed_account: false)
  51. 19 user_filters.each do |filter|
  52. 11 query = query.contacts_of(current_user) if filter == USER_FILTER_CONTACTS
  53. 11 if filter == USER_FILTER_RECEIVING_CONTACTS
  54. 2 query = query.contacts_of(current_user).where(contacts: {receiving: true})
  55. end
  56. 11 if filter == USER_FILTER_SHARING_CONTACTS
  57. 2 query = query.contacts_of(current_user).where(contacts: {sharing: true})
  58. end
  59. 11 if filter.start_with?(USER_FILTER_ASPECTS_PREFIX) # rubocop:disable Style/Next
  60. 6 _, ids = filter.split(":", 2)
  61. 6 ids = ids.split(",").map {|id|
  62. 8 Integer(id) rescue raise("Invalid aspect filter") # rubocop:disable Style/RescueModifier
  63. }
  64. 6 raise "Invalid aspect filter" unless current_user.aspects.where(id: ids).count == ids.size
  65. 4 query = Person.where(id: query.all_from_aspects(ids, current_user).select(:id))
  66. end
  67. end
  68. 13 query.distinct
  69. end
  70. 1 def posts_query
  71. 6 opts = {}
  72. 6 opts[:public_only] = !private_read?
  73. 6 Stream::Tag.new(current_user, params.require(:tag), opts).stream_posts
  74. end
  75. 1 def tags_query
  76. 3 ActsAsTaggableOn::Tag.autocomplete(params.require(:query))
  77. end
  78. 1 def user_filters
  79. 19 @user_filters ||= Array(params[:filter]).uniq.tap do |filters|
  80. 19 raise "Invalid filter" unless filters.all? {|filter|
  81. 15 USER_FILTERS_EXACT_MATCH.include?(filter) ||
  82. 9 USER_FILTERS_PREFIX_MATCH.any? {|prefix| filter.start_with?(prefix) }
  83. }
  84. # For now all filters require contacts:read
  85. 17 require_access_token %w[contacts:read] unless filters.empty?
  86. end
  87. end
  88. 1 def contacts_read?
  89. 18 access_token? %w[contacts:read]
  90. end
  91. end
  92. end
  93. end

app/controllers/api/v1/streams_controller.rb

97.5% lines covered

40 relevant lines. 39 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class StreamsController < Api::V1::BaseController
  5. 1 before_action do
  6. 23 require_access_token %w[public:read]
  7. end
  8. 1 before_action only: %w[aspects] do
  9. 5 require_access_token %w[contacts:read private:read]
  10. end
  11. 1 before_action only: %w[followed_tags] do
  12. 3 require_access_token %w[tags:read]
  13. end
  14. 1 def aspects
  15. 4 aspect_ids = params.has_key?(:aspect_ids) ? JSON.parse(params[:aspect_ids]) : []
  16. 4 @stream = Stream::Aspect.new(current_user, aspect_ids, max_time: stream_max_time)
  17. 4 stream_responder
  18. end
  19. 1 def activity
  20. 3 stream_responder(Stream::Activity, "posts.interacted_at", "interacted_at")
  21. end
  22. 1 def multi
  23. 3 stream_responder(Stream::Multi)
  24. end
  25. 1 def commented
  26. 3 stream_responder(Stream::Comments)
  27. end
  28. 1 def liked
  29. 3 stream_responder(Stream::Likes)
  30. end
  31. 1 def mentions
  32. 3 stream_responder(Stream::Mention)
  33. end
  34. 1 def followed_tags
  35. 2 stream_responder(Stream::FollowedTag)
  36. end
  37. 1 private
  38. 1 def stream_responder(stream_klass=nil, query_time_field="posts.created_at", data_time_field="created_at")
  39. 21 @stream = stream_klass.present? ? stream_klass.new(current_user, max_time: stream_max_time) : @stream
  40. 21 query = @stream.stream_posts
  41. 21 query = query.where(public: true) unless private_read?
  42. 21 posts_page = pager(query, query_time_field, data_time_field).response
  43. 65 posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response }
  44. 21 posts_page[:links].delete(:previous)
  45. 21 render_paged_api_response posts_page
  46. end
  47. 1 def stream_max_time
  48. 21 if params.has_key?("before")
  49. Time.iso8601(params["before"])
  50. else
  51. 21 max_time
  52. end
  53. end
  54. 1 def pager(query, query_time_field, data_time_field)
  55. 21 Api::Paging::RestPaginatorBuilder.new(query, request, true, 15)
  56. .time_pager(params, query_time_field, data_time_field)
  57. end
  58. end
  59. end
  60. end

app/controllers/api/v1/tag_followings_controller.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class TagFollowingsController < Api::V1::BaseController
  5. 1 before_action except: %i[create destroy] do
  6. 5 require_access_token %w[tags:read]
  7. end
  8. 1 before_action only: %i[create destroy] do
  9. 7 require_access_token %w[tags:modify]
  10. end
  11. 1 def index
  12. 4 render json: tag_followings_service.index.pluck(:name)
  13. end
  14. 1 def create
  15. 3 tag_followings_service.create(params.require(:name))
  16. 1 head :no_content
  17. rescue TagFollowingService::DuplicateTag
  18. 1 render_error 409, "Already following this tag"
  19. rescue StandardError
  20. 1 render_error 422, "Failed to process the tag followings request"
  21. end
  22. 1 def destroy
  23. 2 tag_followings_service.destroy_by_name(params.require(:id))
  24. 1 head :no_content
  25. rescue ActiveRecord::RecordNotFound
  26. 1 render_error 410, "Not following this tag"
  27. end
  28. 1 private
  29. 1 def tag_followings_service
  30. 9 @tag_followings_service ||= TagFollowingService.new(current_user)
  31. end
  32. end
  33. end
  34. end

app/controllers/api/v1/users_controller.rb

96.47% lines covered

85 relevant lines. 82 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module V1
  4. 1 class UsersController < Api::V1::BaseController
  5. 1 include TagsHelper
  6. 1 before_action except: %i[contacts update show] do
  7. 17 require_access_token %w[public:read]
  8. end
  9. 1 before_action only: %i[update] do
  10. 5 require_access_token %w[profile:modify]
  11. end
  12. 1 before_action only: %i[contacts] do
  13. 5 require_access_token %w[contacts:read]
  14. end
  15. 1 before_action only: %i[block] do
  16. 9 require_access_token %w[contacts:modify]
  17. end
  18. 1 before_action only: %i[show] do
  19. 7 require_access_token %w[profile]
  20. end
  21. 1 rescue_from ActiveRecord::RecordNotFound do
  22. 5 render_error 404, "User not found"
  23. end
  24. 1 def show
  25. 6 person = if params.has_key?(:id)
  26. 5 found_person = Person.find_by!(guid: params[:id])
  27. 4 raise ActiveRecord::RecordNotFound unless found_person.searchable || access_token?("contacts:read")
  28. 4 found_person
  29. else
  30. 1 current_user.person
  31. end
  32. 5 render json: PersonPresenter.new(person, current_user).profile_hash_as_api_json
  33. end
  34. 1 def update
  35. 4 params_to_update = profile_update_params
  36. 4 if params_to_update && current_user.update_profile(params_to_update)
  37. 4 render json: PersonPresenter.new(current_user.person, current_user).profile_hash_as_api_json
  38. else
  39. render_error 422, "Failed to update the user settings"
  40. end
  41. rescue RuntimeError
  42. render_error 422, "Failed to update the user settings"
  43. end
  44. 1 def contacts
  45. 4 if params.require(:user_id) != current_user.guid
  46. 2 render_error 404, "User not found"
  47. 2 return
  48. end
  49. 2 contacts_query = aspects_service.all_contacts
  50. 2 contacts_page = index_pager(contacts_query).response
  51. 3 contacts_page[:data] = contacts_page[:data].map {|c| PersonPresenter.new(c.person).as_api_json }
  52. 2 render_paged_api_response contacts_page
  53. end
  54. 1 def photos
  55. 4 person = Person.find_by!(guid: params[:user_id])
  56. 3 user_for_query = current_user if private_read?
  57. 3 photos_query = Photo.visible(user_for_query, person, :all, Time.current)
  58. 3 photos_page = time_pager(photos_query).response
  59. 10 photos_page[:data] = photos_page[:data].map {|photo| PhotoPresenter.new(photo).as_api_json(true) }
  60. 3 render_paged_api_response photos_page
  61. end
  62. 1 def posts
  63. 4 person = Person.find_by!(guid: params[:user_id])
  64. 3 posts_query = if private_read?
  65. 2 current_user.posts_from(person, false)
  66. else
  67. 1 Post.where(author_id: person.id, public: true)
  68. end
  69. 3 posts_page = time_pager(posts_query).response
  70. 11 posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response }
  71. 3 render_paged_api_response posts_page
  72. end
  73. 1 def block
  74. 7 person = Person.find_by!(guid: params[:user_id])
  75. 5 service = BlockService.new(current_user)
  76. 5 if request.request_method_symbol == :post
  77. begin
  78. 3 service.block(person)
  79. 2 head :created
  80. rescue ActiveRecord::RecordNotUnique
  81. 1 render_error 409, "User is already blocked"
  82. end
  83. 2 elsif request.request_method_symbol == :delete
  84. begin
  85. 2 service.unblock(person)
  86. 1 head :no_content
  87. rescue ActiveRecord::RecordNotFound
  88. 1 render_error 410, "User is not blocked"
  89. end
  90. else
  91. raise AbstractController::ActionNotFound
  92. end
  93. end
  94. 1 private
  95. 1 def aspects_service
  96. 2 @aspects_service ||= AspectsMembershipService.new(current_user)
  97. end
  98. 1 def profile_update_params
  99. 4 raise RuntimeError if params.has_key?(:id)
  100. 4 updates = params.permit(:bio, :birthday, :gender, :location, :name,
  101. :searchable, :show_profile_info, :nsfw, :tags).to_h || {}
  102. 4 if updates.has_key?(:name)
  103. 1 updates[:first_name] = updates[:name]
  104. 1 updates[:last_name] = nil
  105. 1 updates.delete(:name)
  106. end
  107. 4 if updates.has_key?(:show_profile_info)
  108. 1 updates[:public_details] = updates[:show_profile_info]
  109. 1 updates.delete(:show_profile_info)
  110. end
  111. 4 process_tags_updates(updates)
  112. 4 updates
  113. end
  114. 1 def process_tags_updates(updates)
  115. 4 return unless params.has_key?(:tags)
  116. 1 raise RuntimeError if params[:tags].length > Profile::MAX_TAGS
  117. 3 tags = params[:tags].map {|tag| "#" + normalize_tag_name(tag) }.join(" ")
  118. 1 updates[:tag_string] = tags
  119. 1 updates.delete(:tags)
  120. end
  121. end
  122. end
  123. end

app/controllers/application_controller.rb

100.0% lines covered

82 relevant lines. 82 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class ApplicationController < ActionController::Base
  6. 1 before_action :force_tablet_html
  7. 1 has_mobile_fu
  8. 1 rescue_from ActionController::InvalidAuthenticityToken do
  9. 3 if user_signed_in?
  10. 3 logger.warn "#{current_user.diaspora_handle} CSRF token fail. referer: #{request.referer || 'empty'}"
  11. 3 Workers::Mail::CsrfTokenFail.perform_async(current_user.id)
  12. 3 sign_out current_user
  13. end
  14. 3 flash[:error] = I18n.t("error_messages.csrf_token_fail")
  15. 3 redirect_to new_user_session_path format: request[:format]
  16. end
  17. 1 before_action :ensure_http_referer_is_set
  18. 1 before_action :set_locale
  19. 1 before_action :set_diaspora_header
  20. 1 before_action :mobile_switch
  21. 1 before_action :gon_set_current_user
  22. 1 before_action :gon_set_appconfig
  23. 1 before_action :gon_set_preloads
  24. 1 before_action :configure_permitted_parameters, if: :devise_controller?
  25. 1 helper_method :all_aspects,
  26. :all_contacts_count,
  27. :my_contacts_count,
  28. :only_sharing_count,
  29. :tag_followings,
  30. :tags,
  31. :open_publisher
  32. 185 layout proc { request.format == :mobile ? "application" : "with_header_with_footer" }
  33. 1 private
  34. 1 def default_serializer_options
  35. 522 {root: false}
  36. end
  37. 1 def ensure_http_referer_is_set
  38. 1185 request.env["HTTP_REFERER"] ||= "/"
  39. end
  40. # Overwriting the sign_out redirect path method
  41. 1 def after_sign_out_path_for(resource_or_scope)
  42. 3 is_mobile_device? ? root_path : new_user_session_path
  43. end
  44. 1 def all_aspects
  45. 564 @all_aspects ||= current_user.aspects
  46. end
  47. 1 def all_contacts_count
  48. 9 @all_contacts_count ||= current_user.contacts.count
  49. end
  50. 1 def my_contacts_count
  51. 9 @my_contacts_count ||= current_user.contacts.receiving.count
  52. end
  53. 1 def only_sharing_count
  54. 9 @only_sharing_count ||= current_user.contacts.only_sharing.count
  55. end
  56. 1 def tags
  57. 11 @tags ||= current_user.followed_tags
  58. end
  59. 1 def ensure_page
  60. 13 params[:page] = params[:page] ? params[:page].to_i : 1
  61. end
  62. 1 def set_diaspora_header
  63. 1185 headers["X-Diaspora-Version"] = AppConfig.version_string
  64. 1185 if AppConfig.git_available?
  65. 1185 headers["X-Git-Update"] = AppConfig.git_update if AppConfig.git_update.present?
  66. 1185 headers["X-Git-Revision"] = AppConfig.git_revision if AppConfig.git_revision.present?
  67. end
  68. end
  69. 1 def set_locale
  70. 1185 if user_signed_in?
  71. 621 I18n.locale = current_user.language
  72. else
  73. 564 locale = http_accept_language.language_region_compatible_from AVAILABLE_LANGUAGE_CODES
  74. 564 locale ||= DEFAULT_LANGUAGE
  75. 564 I18n.locale = locale
  76. end
  77. end
  78. 1 def redirect_unless_admin
  79. 26 return if current_user.admin?
  80. 3 redirect_to stream_url, notice: "you need to be an admin to do that"
  81. end
  82. 1 def redirect_unless_moderator
  83. 15 return if current_user.moderator?
  84. 5 redirect_to stream_url, notice: "you need to be an admin or moderator to do that"
  85. end
  86. # use :mobile view for mobile and :html for everything else
  87. # (except if explicitly specified, e.g. :json, :xml)
  88. 1 def mobile_switch
  89. 1185 if session[:mobile_view] == true && request.format.html?
  90. 10 request.format = :mobile
  91. end
  92. end
  93. 1 def force_tablet_html
  94. 1185 session[:tablet_view] = false
  95. end
  96. 1 def after_sign_in_path_for(resource)
  97. 13 stored_location_for(:user) || current_user_redirect_path
  98. end
  99. 1 def max_time
  100. 85 params[:max_time] ? Time.at(params[:max_time].to_i) : Time.now + 1
  101. end
  102. 1 def current_user_redirect_path
  103. # If getting started is active AND the user has not completed the getting_started page
  104. 13 if current_user.getting_started? && !current_user.basic_profile_present?
  105. 8 getting_started_path
  106. else
  107. 5 stream_path
  108. end
  109. end
  110. 1 def gon_set_appconfig
  111. 1185 gon.push(appConfig: {
  112. settings: {podname: AppConfig.settings.pod_name},
  113. map: {mapbox: {
  114. enabled: AppConfig.map.mapbox.enabled?,
  115. access_token: AppConfig.map.mapbox.access_token,
  116. style: AppConfig.map.mapbox.style
  117. }}
  118. })
  119. end
  120. 1 def gon_set_current_user
  121. 1185 return unless user_signed_in?
  122. 621 a_ids = session[:a_ids] || []
  123. 621 user = UserPresenter.new(current_user, a_ids)
  124. 621 gon.push(user: user)
  125. end
  126. 1 def gon_set_preloads
  127. 1185 return unless gon.preloads.nil?
  128. 1156 gon.preloads = {}
  129. end
  130. 1 protected
  131. 1 def configure_permitted_parameters
  132. 30 devise_parameter_sanitizer.permit(:sign_in, keys: [:otp_attempt])
  133. end
  134. end

app/controllers/aspect_memberships_controller.rb

83.33% lines covered

30 relevant lines. 25 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. 1 class AspectMembershipsController < ApplicationController
  7. 1 before_action :authenticate_user!
  8. 1 respond_to :json
  9. 1 def destroy
  10. 2 delete_results = AspectsMembershipService.new(current_user).destroy_by_membership_id(params[:id])
  11. 1 success = delete_results[:success]
  12. 1 membership = delete_results[:membership]
  13. # set the flash message
  14. 1 respond_to do |format|
  15. 1 format.json do
  16. 1 if success
  17. 1 render json: AspectMembershipPresenter.new(membership).base_hash
  18. else
  19. render plain: membership.errors.full_messages, status: 403
  20. end
  21. end
  22. end
  23. end
  24. 1 def create
  25. 7 aspect_membership = AspectsMembershipService.new(current_user).create(params[:aspect_id], params[:person_id])
  26. 5 if aspect_membership
  27. 5 respond_to do |format|
  28. 5 format.json do
  29. 5 render json: AspectMembershipPresenter.new(aspect_membership).base_hash
  30. end
  31. end
  32. else
  33. respond_to do |format|
  34. format.json do
  35. render plain: I18n.t("aspects.add_to_aspect.failure"), status: 409
  36. end
  37. end
  38. end
  39. rescue RuntimeError
  40. 1 respond_to do |format|
  41. 1 format.json do
  42. 1 render plain: I18n.t("aspects.add_to_aspect.failure"), status: :conflict
  43. end
  44. end
  45. end
  46. 1 rescue_from ActiveRecord::StatementInvalid do
  47. 1 render plain: I18n.t("aspect_memberships.destroy.invalid_statement"), status: 400
  48. end
  49. 1 rescue_from ActiveRecord::RecordNotFound do
  50. 1 render plain: I18n.t("aspect_memberships.destroy.no_membership"), status: 404
  51. end
  52. 1 rescue_from Diaspora::NotMine do
  53. render plain: I18n.t("aspect_memberships.destroy.forbidden"), status: 403
  54. end
  55. end

app/controllers/aspects_controller.rb

93.62% lines covered

47 relevant lines. 44 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class AspectsController < ApplicationController
  6. 1 before_action :authenticate_user!
  7. 1 respond_to :html,
  8. :js,
  9. :json
  10. 1 def create
  11. 7 @aspect = current_user.aspects.build(aspect_params)
  12. 7 aspecting_person_id = params[:person_id]
  13. 7 if @aspect.save
  14. 5 result = {id: @aspect.id, name: @aspect.name}
  15. 5 if aspecting_person_id.present?
  16. 3 aspect_membership = connect_person_to_aspect(aspecting_person_id)
  17. 3 result[:aspect_membership] = AspectMembershipPresenter.new(aspect_membership).base_hash if aspect_membership
  18. end
  19. 5 render json: result
  20. else
  21. 2 head :unprocessable_entity
  22. end
  23. end
  24. 1 def destroy
  25. begin
  26. 5 if current_user.auto_follow_back && aspect.id == current_user.auto_follow_back_aspect.id
  27. 3 current_user.update(auto_follow_back: false, auto_follow_back_aspect: nil)
  28. 3 flash[:notice] = I18n.t "aspects.destroy.success_auto_follow_back", name: aspect.name
  29. else
  30. 2 flash[:notice] = I18n.t "aspects.destroy.success", name: aspect.name
  31. end
  32. 5 aspect.destroy
  33. rescue ActiveRecord::StatementInvalid => e
  34. flash[:error] = I18n.t "aspects.destroy.failure", name: aspect.name
  35. end
  36. 5 if request.referer.include?("contacts")
  37. redirect_to contacts_path
  38. else
  39. 5 redirect_to aspects_path
  40. end
  41. end
  42. 1 def show
  43. 2 if aspect
  44. 1 redirect_to aspects_path("a_ids[]" => aspect.id)
  45. else
  46. 1 redirect_to aspects_path
  47. end
  48. end
  49. 1 def update
  50. 2 if aspect.update!(aspect_params)
  51. 2 flash[:notice] = I18n.t "aspects.update.success", name: aspect.name
  52. else
  53. flash[:error] = I18n.t "aspects.update.failure", name: aspect.name
  54. end
  55. 2 render json: {id: aspect.id, name: aspect.name}
  56. end
  57. 1 def update_order
  58. 1 params[:ordered_aspect_ids].each_with_index do |id, i|
  59. 2 current_user.aspects.find(id).update(order_id: i)
  60. end
  61. 1 head :no_content
  62. end
  63. 1 private
  64. 1 def aspect
  65. 24 @aspect ||= current_user.aspects.where(id: params[:id]).first
  66. end
  67. 1 def connect_person_to_aspect(aspecting_person_id)
  68. 3 @person = Person.find(aspecting_person_id)
  69. 3 if @contact = current_user.contact_for(@person)
  70. 1 @contact.aspect_memberships.create(aspect: @aspect)
  71. else
  72. 2 @contact = current_user.share_with(@person, @aspect)
  73. 2 @contact.aspect_memberships.first
  74. end
  75. end
  76. 1 def aspect_params
  77. 9 params.require(:aspect).permit(:name, :order_id)
  78. end
  79. end

app/controllers/blocks_controller.rb

100.0% lines covered

20 relevant lines. 20 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class BlocksController < ApplicationController
  3. 1 before_action :authenticate_user!
  4. 1 def create
  5. begin
  6. 2 block_service.block(Person.find_by!(id: block_params[:person_id]))
  7. rescue ActiveRecord::RecordNotUnique
  8. end
  9. 2 respond_to do |format|
  10. 4 format.json { head :no_content }
  11. 2 format.any { redirect_back fallback_location: root_path }
  12. end
  13. end
  14. 1 def destroy
  15. 7 notice = nil
  16. begin
  17. 7 block_service.remove_block(current_user.blocks.find_by!(id: params[:id]))
  18. 6 notice = {notice: t("blocks.destroy.success")}
  19. rescue ActiveRecord::RecordNotFound
  20. 1 notice = {error: t("blocks.destroy.failure")}
  21. end
  22. 7 respond_to do |format|
  23. 9 format.json { head :no_content }
  24. 12 format.any { redirect_back fallback_location: privacy_settings_path, flash: notice }
  25. end
  26. end
  27. 1 private
  28. 1 def block_params
  29. 2 params.require(:block).permit(:person_id)
  30. end
  31. 1 def block_service
  32. 9 BlockService.new(current_user)
  33. end
  34. end

app/controllers/comments_controller.rb

93.18% lines covered

44 relevant lines. 41 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class CommentsController < ApplicationController
  6. 1 before_action :authenticate_user!, except: :index
  7. 1 respond_to :html, :mobile, :json
  8. 1 rescue_from ActiveRecord::RecordNotFound do
  9. 3 head :not_found
  10. end
  11. 1 rescue_from Diaspora::NonPublic do
  12. 1 authenticate_user!
  13. end
  14. 1 def index
  15. 5 comments = comment_service.find_for_post(params[:post_id])
  16. 2 respond_with do |format|
  17. 3 format.json { render json: CommentPresenter.as_collection(comments, :as_json, current_user), status: :ok }
  18. 3 format.mobile { render layout: false, locals: {comments: comments} }
  19. end
  20. end
  21. 1 def new
  22. respond_to do |format|
  23. format.mobile { render layout: false }
  24. end
  25. end
  26. 1 def create
  27. begin
  28. 6 comment = comment_service.create(params[:post_id], params[:text])
  29. rescue ActiveRecord::RecordNotFound
  30. 1 render plain: I18n.t("comments.create.error"), status: :not_found
  31. 1 return
  32. end
  33. 5 if comment
  34. 5 respond_create_success(comment)
  35. else
  36. render plain: I18n.t("comments.create.error"), status: :unprocessable_entity
  37. end
  38. end
  39. 1 def destroy
  40. 5 if comment_service.destroy(params[:id])
  41. 3 respond_destroy_success
  42. else
  43. 1 respond_destroy_error
  44. end
  45. end
  46. 1 private
  47. 1 def comment_service
  48. 16 @comment_service ||= CommentService.new(current_user)
  49. end
  50. 1 def respond_create_success(comment)
  51. 5 respond_to do |format|
  52. 6 format.json { render json: CommentPresenter.new(comment), status: 201 }
  53. 8 format.html { head :created }
  54. 6 format.mobile { render partial: "comment", locals: {comment: comment} }
  55. end
  56. end
  57. 1 def respond_destroy_success
  58. 3 respond_to do |format|
  59. 3 format.mobile { redirect_back fallback_location: stream_path }
  60. 6 format.js { head :no_content }
  61. 3 format.json { head :no_content }
  62. end
  63. end
  64. 1 def respond_destroy_error
  65. 1 respond_to do |format|
  66. 1 format.mobile { redirect_back fallback_location: stream_path }
  67. 2 format.js { head :forbidden }
  68. 1 format.json { head :forbidden }
  69. end
  70. end
  71. end

app/controllers/contacts_controller.rb

88.89% lines covered

54 relevant lines. 48 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class ContactsController < ApplicationController
  6. 1 before_action :authenticate_user!
  7. 1 def index
  8. 22 respond_to do |format|
  9. # Used for normal requests to contacts#index
  10. 29 format.html { set_up_contacts }
  11. # Used by the mobile site
  12. 23 format.mobile { set_up_contacts_mobile }
  13. # Used for mentions in the publisher and pagination on the contacts page
  14. 22 format.json {
  15. 14 @people = if params[:q].present?
  16. 6 mutual = params[:mutual].present? && params[:mutual]
  17. 6 Person.search(params[:q], current_user, only_contacts: true, mutual: mutual).limit(15)
  18. else
  19. 8 set_up_contacts_json
  20. end
  21. 14 render json: @people
  22. }
  23. end
  24. end
  25. 1 def spotlight
  26. 2 @spotlight = true
  27. 2 @people = Person.community_spotlight
  28. end
  29. 1 private
  30. 1 def set_up_contacts
  31. 7 if params[:a_id].present?
  32. 1 @aspect = current_user.aspects.find(params[:a_id])
  33. 1 gon.preloads[:aspect] = AspectPresenter.new(@aspect).as_json
  34. end
  35. 7 @contacts_size = current_user.contacts.size
  36. end
  37. 1 def set_up_contacts_json
  38. 8 type = params[:set].presence
  39. 8 if params[:a_id].present?
  40. 4 type ||= "by_aspect"
  41. 4 @aspect = current_user.aspects.find(params[:a_id])
  42. end
  43. 8 type ||= "receiving"
  44. 8 contacts_by_type(type).paginate(page: params[:page], per_page: 25)
  45. 19 .map {|c| ContactPresenter.new(c, current_user).full_hash_with_person }
  46. end
  47. 1 def contacts_by_type(type)
  48. 8 order = ["profiles.first_name ASC", "profiles.last_name ASC", "profiles.diaspora_handle ASC"]
  49. 8 contacts = case type
  50. when "all"
  51. 2 order.unshift "receiving DESC"
  52. 2 current_user.contacts
  53. when "only_sharing"
  54. current_user.contacts.only_sharing
  55. when "receiving"
  56. 2 current_user.contacts.receiving
  57. when "by_aspect"
  58. 4 order.unshift Arel.sql("contact_id IS NOT NULL DESC")
  59. 4 contacts_by_aspect(@aspect.id)
  60. else
  61. raise ArgumentError, "unknown type #{type}"
  62. end
  63. 8 contacts.includes(person: :profile)
  64. .order(order)
  65. end
  66. 1 def contacts_by_aspect(aspect_id)
  67. 4 contacts = current_user.contacts.arel_table
  68. 4 aspect_memberships = AspectMembership.arel_table
  69. 4 current_user.contacts.joins(
  70. contacts.outer_join(aspect_memberships).on(
  71. aspect_memberships[:aspect_id].eq(aspect_id).and(
  72. aspect_memberships[:contact_id].eq(contacts[:id])
  73. )
  74. ).join_sources
  75. )
  76. end
  77. 1 def set_up_contacts_mobile
  78. 1 @contacts = case params[:set]
  79. when "only_sharing"
  80. current_user.contacts.only_sharing
  81. when "all"
  82. current_user.contacts
  83. else
  84. 1 if params[:a_id]
  85. @aspect = current_user.aspects.find(params[:a_id])
  86. @aspect.contacts
  87. else
  88. 1 current_user.contacts.receiving
  89. end
  90. end
  91. 1 @contacts = @contacts.for_a_stream.paginate(:page => params[:page], :per_page => 25)
  92. 1 @contacts_size = @contacts.length
  93. end
  94. end

app/controllers/conversation_visibilities_controller.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. 1 class ConversationVisibilitiesController < ApplicationController
  7. 1 before_action :authenticate_user!
  8. 1 def destroy
  9. 5 @vis = ConversationVisibility.where(:person_id => current_user.person.id,
  10. :conversation_id => params[:conversation_id]).first
  11. 5 if @vis
  12. 4 participants = @vis.conversation.participants.count
  13. 4 if @vis.destroy
  14. 4 if participants == 1
  15. 1 flash[:notice] = I18n.t('conversations.destroy.delete_success')
  16. else
  17. 3 flash[:notice] = I18n.t('conversations.destroy.hide_success')
  18. end
  19. end
  20. end
  21. 5 redirect_to conversations_path
  22. end
  23. end

app/controllers/conversations_controller.rb

95.24% lines covered

63 relevant lines. 60 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ConversationsController < ApplicationController
  3. 1 before_action :authenticate_user!
  4. 1 respond_to :html, :mobile, :json, :js
  5. 1 def index
  6. 8 @visibilities = ConversationVisibility.includes(:conversation)
  7. .order("conversations.updated_at DESC")
  8. .where(person_id: current_user.person_id)
  9. .paginate(page: params[:page], per_page: 15)
  10. 8 if params[:conversation_id]
  11. 4 @conversation = Conversation.joins(:conversation_visibilities)
  12. .where(conversation_visibilities: {
  13. person_id: current_user.person_id,
  14. conversation_id: params[:conversation_id]
  15. }).first
  16. 4 if @conversation
  17. 3 @first_unread_message_id = @conversation.first_unread_message(current_user).try(:id)
  18. 3 @conversation.set_read(current_user)
  19. end
  20. end
  21. 8 gon.contacts = contacts_data
  22. 8 respond_with do |format|
  23. 15 format.html { render "index", locals: {no_contacts: current_user.contacts.mutual.empty?} }
  24. 9 format.json { render json: @visibilities.map(&:conversation), status: 200 }
  25. end
  26. end
  27. 1 def create
  28. # Contacts autocomplete does not work the same way on mobile and desktop
  29. # Mobile returns contact ids array while desktop returns person id
  30. # This will have to be removed when mobile autocomplete is ported to Typeahead
  31. 106 recipients_param, column = [%i(contact_ids id), %i(person_ids person_id)].find {|param, _| params[param].present? }
  32. 40 if recipients_param
  33. 28 person_ids = current_user.contacts.mutual.where(column => params[recipients_param].split(",")).pluck(:person_id)
  34. end
  35. 40 unless person_ids.present?
  36. 18 render plain: I18n.t("javascripts.conversation.create.no_recipient"), status: 422
  37. 18 return
  38. end
  39. 22 opts = params.require(:conversation).permit(:subject)
  40. 22 opts[:participant_ids] = person_ids
  41. 22 opts[:message] = { text: params[:conversation][:text] }
  42. 22 @conversation = current_user.build_conversation(opts)
  43. 22 if @conversation.save
  44. 16 Diaspora::Federation::Dispatcher.defer_dispatch(current_user, @conversation)
  45. 16 flash[:notice] = I18n.t("conversations.create.sent")
  46. 16 render json: {id: @conversation.id}
  47. else
  48. 6 render plain: I18n.t("conversations.create.fail"), status: 422
  49. end
  50. end
  51. 1 def show
  52. 2 respond_to do |format|
  53. 2 format.html do
  54. 1 redirect_to conversations_path(conversation_id: params[:id])
  55. 1 return
  56. end
  57. 2 if @conversation = current_user.conversations.where(id: params[:id]).first
  58. 2 @first_unread_message_id = @conversation.first_unread_message(current_user).try(:id)
  59. 2 @conversation.set_read(current_user)
  60. 3 format.json { render :json => @conversation, :status => 200 }
  61. else
  62. redirect_to conversations_path
  63. end
  64. end
  65. end
  66. 1 def raw
  67. 2 @conversation = current_user.conversations.where(id: params[:conversation_id]).first
  68. 2 if @conversation
  69. 1 @first_unread_message_id = @conversation.first_unread_message(current_user).try(:id)
  70. 1 @conversation.set_read(current_user)
  71. 1 render partial: "conversations/show", locals: {conversation: @conversation}
  72. else
  73. 1 head :not_found
  74. end
  75. end
  76. 1 def new
  77. 7 if !params[:modal] && !session[:mobile_view] && request.format.html?
  78. 1 redirect_to conversations_path
  79. 1 return
  80. end
  81. 6 if session[:mobile_view] == true && request.format.html?
  82. 5 @contacts_json = contacts_data.to_json
  83. 5 @contact_ids = if params[:contact_id]
  84. current_user.contacts.find(params[:contact_id]).id
  85. 5 elsif params[:aspect_id]
  86. current_user.aspects.find(params[:aspect_id]).contacts.pluck(:id).join(",")
  87. end
  88. 5 render :layout => true
  89. else
  90. 1 render :layout => false
  91. end
  92. end
  93. 1 private
  94. 1 def contacts_data
  95. 13 current_user.contacts.mutual.joins(person: :profile)
  96. .pluck(*%w(contacts.id profiles.first_name profiles.last_name people.diaspora_handle))
  97. .map {|contact_id, *name_attrs|
  98. 13 {value: contact_id, name: ERB::Util.h(Person.name_from_attrs(*name_attrs)) }
  99. }
  100. end
  101. end

app/controllers/help_controller.rb

100.0% lines covered

1 relevant lines. 1 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class HelpController < ApplicationController
  3. end

app/controllers/home_controller.rb

90.91% lines covered

22 relevant lines. 20 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class HomeController < ApplicationController
  6. 1 def show
  7. 4 partial_dir = Rails.root.join("app", "views", "home")
  8. 4 if user_signed_in?
  9. 1 redirect_to stream_path
  10. 3 elsif request.format == :mobile
  11. 1 if partial_dir.join("_show.mobile.haml").exist? ||
  12. partial_dir.join("_show.mobile.erb").exist? ||
  13. partial_dir.join("_show.haml").exist?
  14. render :show
  15. else
  16. 1 redirect_to user_session_path
  17. end
  18. 2 elsif partial_dir.join("_show.html.haml").exist? ||
  19. partial_dir.join("_show.html.erb").exist? ||
  20. partial_dir.join("_show.haml").exist?
  21. render :show
  22. 2 elsif Role.admins.any?
  23. 1 render :default
  24. else
  25. 1 redirect_to podmin_path
  26. end
  27. end
  28. 1 def podmin
  29. 2 render :podmin
  30. end
  31. 1 def toggle_mobile
  32. 2 session[:mobile_view] = session[:mobile_view].nil? ? true : !session[:mobile_view]
  33. 2 redirect_back fallback_location: root_path
  34. end
  35. 1 def force_mobile
  36. 2 session[:mobile_view] = true
  37. 2 redirect_to stream_path
  38. end
  39. end

app/controllers/invitation_codes_controller.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class InvitationCodesController < ApplicationController
  3. 1 before_action :ensure_valid_invite_code
  4. 1 rescue_from ActiveRecord::RecordNotFound do
  5. 1 redirect_to root_url, :notice => I18n.t('invitation_codes.not_valid')
  6. end
  7. 1 def show
  8. 2 if user_signed_in?
  9. 1 invite = InvitationCode.find_by_token!(params[:id])
  10. 1 flash[:notice] = I18n.t("invitation_codes.already_logged_in", inviter: invite.user.name)
  11. 1 redirect_to person_path(invite.user.person)
  12. else
  13. 1 redirect_to new_user_registration_path(invite: {token: params[:id]})
  14. end
  15. end
  16. 1 private
  17. 1 def ensure_valid_invite_code
  18. 3 InvitationCode.find_by_token!(params[:id])
  19. end
  20. end

app/controllers/invitations_controller.rb

100.0% lines covered

42 relevant lines. 42 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class InvitationsController < ApplicationController
  6. 1 before_action :authenticate_user!
  7. 1 before_action :check_invitations_available!, only: :create
  8. 1 def new
  9. 1 @invite_code = current_user.invitation_code
  10. 1 @invalid_emails = html_safe_string_from_session_array(:invalid_email_invites)
  11. 1 @valid_emails = html_safe_string_from_session_array(:valid_email_invites)
  12. 1 respond_to do |format|
  13. 1 format.html do
  14. 1 render "invitations/new", layout: false
  15. end
  16. end
  17. end
  18. 1 def create
  19. 13 emails = inviter_params[:emails].split(",").map(&:strip).uniq
  20. 29 valid_emails, invalid_emails = emails.partition {|email| valid_email?(email) }
  21. 13 session[:valid_email_invites] = valid_emails
  22. 13 session[:invalid_email_invites] = invalid_emails
  23. 13 unless valid_emails.empty?
  24. 7 Workers::Mail::InviteEmail.perform_async(valid_emails.join(","), current_user.id, inviter_params.to_h)
  25. end
  26. 13 if emails.empty?
  27. 3 flash[:error] = t("invitations.create.empty")
  28. 10 elsif invalid_emails.empty?
  29. 4 flash[:notice] = t("invitations.create.sent", emails: valid_emails.join(", "))
  30. 6 elsif valid_emails.empty?
  31. 3 flash[:error] = t("invitations.create.rejected", emails: invalid_emails.join(", "))
  32. else
  33. 3 flash[:error] = t("invitations.create.sent", emails: valid_emails.join(", ")) + ". " +
  34. t("invitations.create.rejected", emails: invalid_emails.join(", "))
  35. end
  36. 13 redirect_back fallback_location: stream_path
  37. end
  38. 1 private
  39. 1 def check_invitations_available!
  40. 15 return true if AppConfig.settings.enable_registrations? || current_user.invitation_code.can_be_used?
  41. 2 flash[:error] = if AppConfig.settings.invitations.open?
  42. 1 t("invitations.create.no_more")
  43. else
  44. 1 t("invitations.create.closed")
  45. end
  46. 2 redirect_back fallback_location: stream_path
  47. end
  48. 1 def valid_email?(email)
  49. 19 User.email_regexp.match(email).present?
  50. end
  51. 1 def html_safe_string_from_session_array(key)
  52. 6 return "" unless session[key].present?
  53. 3 return "" unless session[key].respond_to?(:join)
  54. 2 value = session[key].join(", ").html_safe
  55. 2 session[key] = nil
  56. 2 value
  57. end
  58. 1 def inviter_params
  59. 20 params.require(:email_inviter).permit(:message, :locale, :emails).to_h
  60. end
  61. end

app/controllers/likes_controller.rb

92.59% lines covered

27 relevant lines. 25 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class LikesController < ApplicationController
  6. 1 include ApplicationHelper
  7. 1 before_action :authenticate_user!, except: :index
  8. 1 respond_to :html,
  9. :mobile,
  10. :json
  11. 1 rescue_from Diaspora::NonPublic do
  12. 1 authenticate_user!
  13. end
  14. 1 def index
  15. 5 like = if params[:post_id]
  16. 5 like_service.find_for_post(params[:post_id])
  17. else
  18. like_service.find_for_comment(params[:comment_id])
  19. end
  20. 3 render json: like
  21. .includes(author: :profile)
  22. .as_api_response(:backbone)
  23. end
  24. 1 def create
  25. 6 like = if params[:post_id]
  26. 6 like_service.create_for_post(params[:post_id])
  27. else
  28. like_service.create_for_comment(params[:comment_id])
  29. end
  30. rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
  31. 3 render plain: I18n.t("likes.create.error"), status: 422
  32. else
  33. 2 respond_to do |format|
  34. 3 format.html { head :created }
  35. 2 format.mobile { redirect_to post_path(like.post_id) }
  36. 3 format.json { render json: like.as_api_response(:backbone), status: 201 }
  37. end
  38. end
  39. 1 def destroy
  40. 2 if like_service.destroy(params[:id])
  41. 1 head :no_content
  42. else
  43. 1 render plain: I18n.t("likes.destroy.error"), status: 404
  44. end
  45. end
  46. 1 private
  47. 1 def like_service
  48. 13 @like_service ||= LikeService.new(current_user)
  49. end
  50. end

app/controllers/links_controller.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class LinksController < ApplicationController
  3. 1 def resolve
  4. 6 entity = DiasporaLinkService.new(query).find_or_fetch_entity
  5. 6 raise ActiveRecord::RecordNotFound if entity.nil?
  6. 4 redirect_to url_for(entity)
  7. end
  8. 1 private
  9. 1 def query
  10. 6 @query ||= params.fetch(:q)
  11. end
  12. end

app/controllers/manifest_controller.rb

0.0% lines covered

25 relevant lines. 0 lines covered and 25 lines missed.
    
  1. # frozen_string_literal: true
  2. class ManifestController < ApplicationController
  3. def show # rubocop:disable Metrics/MethodLength
  4. render json: {
  5. short_name: AppConfig.settings.pod_name,
  6. name: AppConfig.settings.pod_name,
  7. description: "diaspora* is a free, decentralized and privacy-respecting social network",
  8. icons: [
  9. {
  10. src: helpers.image_path("branding/logos/app-icon.png"),
  11. type: "image/png",
  12. sizes: "192x192"
  13. },
  14. {
  15. src: helpers.image_path("branding/logos/app-icon-512.png"),
  16. type: "image/png",
  17. sizes: "512x512"
  18. }
  19. ],
  20. start_url: "/",
  21. background_color: "#000000",
  22. display: "standalone",
  23. theme_color: "#000000"
  24. }
  25. end
  26. end

app/controllers/messages_controller.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class MessagesController < ApplicationController
  6. 1 before_action :authenticate_user!
  7. 1 respond_to :html, :mobile
  8. 1 respond_to :json, :only => :show
  9. 1 def create
  10. 8 conversation = Conversation.find(params[:conversation_id])
  11. 8 opts = params.require(:message).permit(:text)
  12. 8 message = current_user.build_message(conversation, opts)
  13. 8 if message.save
  14. 6 logger.info "event=create type=message user=#{current_user.diaspora_handle} status=success " \
  15. "message=#{message.id} chars=#{params[:message][:text].length}"
  16. 6 Diaspora::Federation::Dispatcher.defer_dispatch(current_user, message)
  17. else
  18. 2 flash[:error] = I18n.t('conversations.new_conversation.fail')
  19. end
  20. 8 redirect_to conversations_path(:conversation_id => conversation.id)
  21. end
  22. end

app/controllers/node_info_controller.rb

92.86% lines covered

14 relevant lines. 13 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class NodeInfoController < ApplicationController
  3. 1 def jrd
  4. 2 render json: NodeInfo.jrd(CGI.unescape(node_info_url("123.123").sub("123.123", "%{version}")))
  5. end
  6. 1 def document
  7. 10 if NodeInfo.supported_version?(params[:version])
  8. 9 document = NodeInfoPresenter.new(params[:version])
  9. 9 render json: document, content_type: document.content_type
  10. else
  11. 1 head :not_found
  12. end
  13. end
  14. 1 def statistics
  15. 3 respond_to do |format|
  16. 4 format.json { head :not_acceptable }
  17. 5 format.all { @statistics = NodeInfoPresenter.new("1.0") }
  18. end
  19. end
  20. # TODO: this is only a dummy endpoint, because old versions of the ConnectionTester (<= 0.7.17.0)
  21. # checked for this endpoint. Remove this endpoint again once most pods are updated to >= 0.7.18.0
  22. 1 def host_meta
  23. render xml: <<~XML
  24. <?xml version="1.0" encoding="UTF-8"?>
  25. <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
  26. </XRD>
  27. XML
  28. end
  29. end

app/controllers/notifications_controller.rb

100.0% lines covered

50 relevant lines. 50 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class NotificationsController < ApplicationController
  6. 1 before_action :authenticate_user!
  7. 1 def index
  8. 15 conditions = {recipient_id: current_user.id}
  9. 15 types = NotificationService::NOTIFICATIONS_JSON_TYPES
  10. 15 if params[:type] && types.has_key?(params[:type])
  11. 1 conditions[:type] = types[params[:type]]
  12. end
  13. 15 if params[:show] == "unread" then conditions[:unread] = true end
  14. 15 page = params[:page] || 1
  15. 15 per_page = params[:per_page] || 25
  16. 15 @notifications = WillPaginate::Collection.create(page, per_page, Notification.where(conditions).count ) do |pager|
  17. 15 result = Notification.where(conditions)
  18. .includes(:target, actors: :profile)
  19. .order("updated_at desc")
  20. .limit(pager.per_page)
  21. .offset(pager.offset)
  22. 15 pager.replace(result)
  23. end
  24. 64 @group_days = @notifications.group_by {|note| note.updated_at.strftime("%Y-%m-%d") }
  25. 15 @unread_notification_count = current_user.unread_notifications.count
  26. 15 @grouped_unread_notification_counts = {}
  27. 15 types.each_with_object(current_user.unread_notifications.group_by(&:type)) {|(name, type), notifications|
  28. 135 @grouped_unread_notification_counts[name] = notifications.has_key?(type) ? notifications[type].count : 0
  29. }
  30. 15 respond_to do |format|
  31. 15 format.html
  32. 15 format.xml { render xml: @notifications.to_xml }
  33. 15 format.json {
  34. 2 render json: render_as_json(@unread_notification_count, @grouped_unread_notification_counts, @notifications)
  35. }
  36. end
  37. end
  38. 1 def update
  39. 5 note = Notification.where(recipient_id: current_user.id, id: params[:id]).first
  40. 5 if note
  41. 4 note.set_read_state(params[:set_unread] != "true")
  42. 4 respond_to do |format|
  43. 8 format.json { render json: {guid: note.id, unread: note.unread} }
  44. end
  45. else
  46. 1 respond_to do |format|
  47. 2 format.json { render json: {}.to_json }
  48. end
  49. end
  50. end
  51. 1 def default_serializer_options
  52. {
  53. 12 context: self,
  54. root: false
  55. }
  56. end
  57. 1 def read_all
  58. 9 current_type = NotificationService::NOTIFICATIONS_JSON_TYPES[params[:type]]
  59. 9 notifications = Notification.where(recipient_id: current_user.id, unread: true)
  60. 9 notifications = notifications.where(type: current_type) if params[:type]
  61. 9 notifications.update_all(unread: false)
  62. 9 respond_to do |format|
  63. 9 if current_user.unread_notifications.count > 0
  64. 5 format.html { redirect_to notifications_path }
  65. 4 format.mobile { redirect_to notifications_path }
  66. else
  67. 10 format.html { redirect_to stream_path }
  68. 7 format.mobile { redirect_to stream_path }
  69. end
  70. 9 format.xml { render xml: {}.to_xml }
  71. 10 format.json { render json: {}.to_json }
  72. end
  73. end
  74. 1 private
  75. 1 def render_as_json(unread_count, unread_count_by_type, notification_list)
  76. {
  77. 2 unread_count: unread_count,
  78. unread_count_by_type: unread_count_by_type,
  79. notification_list: notification_list.map {|note|
  80. 4 NotificationSerializer.new(note, default_serializer_options).as_json
  81. }
  82. }.as_json
  83. end
  84. end

app/controllers/participations_controller.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ParticipationsController < ApplicationController
  3. 1 before_action :authenticate_user!
  4. 1 def create
  5. 4 post = current_user.find_visible_shareable_by_id(Post, params[:post_id])
  6. 4 if post
  7. 3 current_user.participate! post
  8. 3 head :created
  9. else
  10. 1 head :forbidden
  11. end
  12. end
  13. 1 def destroy
  14. 2 participation = current_user.participations.find_by target_id: params[:post_id]
  15. 2 if participation
  16. 1 participation.destroy
  17. 1 head :ok
  18. else
  19. 1 head :unprocessable_entity
  20. end
  21. end
  22. end

app/controllers/people_controller.rb

100.0% lines covered

88 relevant lines. 88 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class PeopleController < ApplicationController
  6. 1 include GonHelper
  7. 1 before_action :authenticate_user!, except: %i(show stream hovercard)
  8. 1 before_action :find_person, only: %i(show stream hovercard)
  9. 1 before_action :authenticate_if_remote_profile!, only: %i(show stream)
  10. 1 respond_to :html
  11. 1 respond_to :json, :only => [:index, :show]
  12. 1 rescue_from ActiveRecord::RecordNotFound do
  13. 3 render file: Rails.root.join("public/404.html").to_s, format: :html, layout: false, status: :not_found
  14. end
  15. 1 rescue_from Diaspora::AccountClosed do
  16. 1 respond_to do |format|
  17. 2 format.any { redirect_back fallback_location: root_path, notice: t("people.show.closed_account") }
  18. 1 format.json { head :gone }
  19. end
  20. end
  21. 1 helper_method :search_query
  22. 1 def index
  23. 21 @aspect = :search
  24. 21 limit = params[:limit] ? params[:limit].to_i : 15
  25. 21 @people = Person.search(search_query, current_user)
  26. 21 respond_to do |format|
  27. 21 format.json do
  28. 5 @people = @people.limit(limit)
  29. 5 render :json => @people
  30. end
  31. 21 format.any(:html, :mobile) do
  32. # only do it if it is a diaspora*-ID
  33. 16 if diaspora_id?(search_query)
  34. 6 @people = Person.where(diaspora_handle: search_query.downcase, closed_account: false)
  35. 6 background_search(search_query) if @people.empty?
  36. end
  37. 16 @people = @people.paginate(:page => params[:page], :per_page => 15)
  38. 16 @hashes = hashes_for_people(@people, @aspects)
  39. end
  40. end
  41. end
  42. 1 def refresh_search
  43. 4 @aspect = :search
  44. 4 @people = Person.where(diaspora_handle: search_query.downcase, closed_account: false)
  45. 4 @answer_html = ""
  46. 4 unless @people.empty?
  47. 1 @hashes = hashes_for_people(@people, @aspects)
  48. 1 self.formats = self.formats + [:html]
  49. 1 @answer_html = render_to_string :partial => 'people/person', :locals => @hashes.first
  50. end
  51. 4 render json: {search_html: @answer_html, contacts: gon.preloads[:contacts]}.to_json
  52. end
  53. # renders the persons user profile page
  54. 1 def show
  55. 28 mark_corresponding_notifications_read if user_signed_in?
  56. 28 @presenter = PersonPresenter.new(@person, current_user)
  57. 28 respond_to do |format|
  58. 28 format.all do
  59. 24 if user_signed_in?
  60. 19 @contact = current_user.contact_for(@person)
  61. end
  62. 24 gon.preloads[:person] = @presenter.as_json
  63. 24 gon.preloads[:photos_count] = Photo.visible(current_user, @person).count(:all)
  64. 24 respond_with @presenter, layout: "with_header"
  65. end
  66. 28 format.mobile do
  67. 4 @post_type = :all
  68. 4 person_stream
  69. 4 respond_with @presenter
  70. end
  71. 28 format.json { render json: @presenter.as_json }
  72. end
  73. end
  74. 1 def stream
  75. 10 respond_to do |format|
  76. 11 format.all { redirect_to person_path(@person) }
  77. 10 format.json {
  78. 24 render json: person_stream.stream_posts.map { |p| LastThreeCommentsDecorator.new(PostPresenter.new(p, current_user)) }
  79. }
  80. end
  81. end
  82. # hovercards fetch some the persons public profile data via json and display
  83. # it next to the avatar image in a nice box
  84. 1 def hovercard
  85. 5 respond_to do |format|
  86. 5 format.all do
  87. 1 redirect_to :action => "show", :id => params[:person_id]
  88. end
  89. 5 format.json do
  90. 4 render json: PersonPresenter.new(@person, current_user).hovercard
  91. end
  92. end
  93. end
  94. 1 private
  95. 1 def find_person
  96. 48 username = params[:username]
  97. 48 @person = Person.find_from_guid_or_username(
  98. id: params[:id] || params[:person_id],
  99. username: username
  100. )
  101. 45 raise ActiveRecord::RecordNotFound if @person.nil?
  102. 45 raise Diaspora::AccountClosed if @person.closed_account?
  103. end
  104. 1 def background_search(search_query)
  105. 3 Workers::FetchWebfinger.perform_async(search_query)
  106. 3 @background_query = search_query.downcase
  107. 3 gon.preloads[:background_query] = @background_query
  108. end
  109. 1 def hashes_for_people(people, aspects)
  110. 17 people.map {|person|
  111. {
  112. 10 person: person,
  113. contact: current_user.contact_for(person) || Contact.new(person: person),
  114. aspects: aspects
  115. }.tap {|hash|
  116. 10 gon_load_contact(hash[:contact])
  117. }
  118. }
  119. end
  120. 1 def search_query
  121. 109 @search_query ||= params[:q] || params[:term] || ''
  122. end
  123. 1 def diaspora_id?(query)
  124. 24 !(query.nil? || query.lstrip.empty?) && Validation::Rule::DiasporaId.new.valid_value?(query.downcase).present?
  125. end
  126. # view this profile on the home pod, if you don't want to sign in...
  127. 1 def authenticate_if_remote_profile!
  128. 39 authenticate_user! if @person.try(:remote?)
  129. end
  130. 1 def mark_corresponding_notifications_read
  131. 22 Notification.where(recipient_id: current_user.id, target_type: "Person", target_id: @person.id, unread: true).each do |n|
  132. 1 n.set_read_state( true )
  133. end
  134. end
  135. 1 def person_stream
  136. 13 @stream ||= Stream::Person.new(current_user, @person, max_time: max_time)
  137. end
  138. end

app/controllers/photos_controller.rb

80.22% lines covered

91 relevant lines. 73 lines covered and 18 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class PhotosController < ApplicationController
  6. 1 before_action :authenticate_user!, except: %i[show index]
  7. 1 respond_to :html, :json
  8. 1 def show
  9. 5 @photo = if user_signed_in?
  10. 4 current_user.photos_from(Person.find_by(guid: params[:person_id])).where(id: params[:id]).first
  11. else
  12. 1 Photo.where(id: params[:id], public: true).first
  13. end
  14. 5 raise ActiveRecord::RecordNotFound unless @photo
  15. 3 respond_to do |format|
  16. 3 format.html {
  17. 2 post = @photo.status_message
  18. 2 redirect_to post ? post_path(post) : @photo.url
  19. }
  20. 4 format.mobile { render "photos/show" }
  21. end
  22. end
  23. 1 def index
  24. 17 @post_type = :photos
  25. 17 @person = Person.find_by(guid: params[:person_id])
  26. 17 authenticate_user! if @person.try(:remote?) && !user_signed_in?
  27. 16 @presenter = PersonPresenter.new(@person, current_user)
  28. 16 if @person
  29. 16 @contact = current_user.contact_for(@person) if user_signed_in?
  30. 16 @posts = Photo.visible(current_user, @person, :all, max_time)
  31. 16 respond_to do |format|
  32. 16 format.all do
  33. 11 gon.preloads[:person] = @presenter.as_json
  34. 11 gon.preloads[:photos_count] = Photo.visible(current_user, @person).count(:all)
  35. 11 render "people/show", layout: "with_header"
  36. end
  37. 19 format.mobile { render "people/show" }
  38. 18 format.json { render_for_api :backbone, json: @posts, root: :photos }
  39. end
  40. else
  41. flash[:error] = I18n.t "people.show.does_not_exist"
  42. redirect_to people_path
  43. end
  44. end
  45. 1 def create
  46. 7 rescuing_photo_errors do
  47. 7 legacy_create
  48. end
  49. end
  50. 1 def make_profile_photo
  51. 3 author_id = current_user.person_id
  52. 3 @photo = Photo.where(id: params[:photo_id], author_id: author_id).first
  53. 3 if @photo
  54. 2 profile_hash = {image_url: @photo.url(:thumb_large),
  55. image_url_medium: @photo.url(:thumb_medium),
  56. image_url_small: @photo.url(:thumb_small)}
  57. 2 if current_user.update_profile(profile_hash)
  58. 2 respond_to do |format|
  59. 2 format.js {
  60. 2 render json: {photo_id: @photo.id,
  61. image_url: @photo.url(:thumb_large),
  62. image_url_medium: @photo.url(:thumb_medium),
  63. image_url_small: @photo.url(:thumb_small),
  64. author_id: author_id},
  65. status: 201
  66. }
  67. end
  68. else
  69. head :unprocessable_entity
  70. end
  71. else
  72. 1 head :unprocessable_entity
  73. end
  74. end
  75. 1 def destroy
  76. 5 photo = current_user.photos.where(id: params[:id]).first
  77. 5 if photo
  78. 3 current_user.retract(photo)
  79. 3 respond_to do |format|
  80. 4 format.json { head :no_content }
  81. 3 format.html do
  82. 2 flash[:notice] = I18n.t "photos.destroy.notice"
  83. 2 if StatusMessage.find_by(guid: photo.status_message_guid)
  84. respond_with photo, location: post_path(photo.status_message)
  85. else
  86. 2 respond_with photo, location: person_photos_path(current_user.person)
  87. end
  88. end
  89. end
  90. else
  91. 2 respond_with photo, location: person_photos_path(current_user.person)
  92. end
  93. end
  94. 1 private
  95. 1 def photo_params
  96. 7 params.require(:photo).permit(:public, :text, :pending, :user_file, :image_url, :aspect_ids, :set_profile_photo)
  97. end
  98. 1 def file_handler(params)
  99. # For XHR file uploads, request.params[:qqfile] will be the path to the temporary file
  100. # For regular form uploads (such as those made by Opera), request.params[:qqfile] will be an
  101. # UploadedFile which can be returned unaltered.
  102. 3 if !request.params[:qqfile].is_a?(String)
  103. 3 qqfile = params[:qqfile]
  104. # Cropped or manipulated files have their real filename only in qqfilename. Take care of this.
  105. 3 qqfile.original_filename = params[:qqfilename] if qqfile.original_filename == "blob"
  106. 3 qqfile
  107. else
  108. ######################## dealing with local files #############
  109. # get file name
  110. file_name = params[:qqfile]
  111. # get file content type
  112. att_content_type = request.content_type.to_s == "" ? "application/octet-stream" : request.content_type.to_s
  113. # create temporal file
  114. file = Tempfile.new(file_name, encoding: "BINARY")
  115. # put data into this file from raw post request
  116. file.print request.raw_post.force_encoding("BINARY")
  117. # create several required methods for this temporal file
  118. Tempfile.send(:define_method, "content_type") { return att_content_type }
  119. Tempfile.send(:define_method, "original_filename") { return file_name }
  120. file
  121. end
  122. end
  123. 1 def legacy_create
  124. 7 base_params = photo_params
  125. 7 uploaded_file = file_handler(params)
  126. 7 @photo = photo_service.create_from_params_and_file(base_params, uploaded_file)
  127. 7 if @photo
  128. 7 respond_to do |format|
  129. 8 format.json { render(layout: false, json: {"success" => true, "data" => @photo}.to_json) }
  130. 13 format.html { render(layout: false, json: {"success" => true, "data" => @photo}.to_json) }
  131. end
  132. else
  133. respond_with @photo, location: photos_path, error: message
  134. end
  135. end
  136. 1 def rescuing_photo_errors
  137. 7 yield
  138. rescue TypeError
  139. return_photo_error I18n.t "photos.create.type_error"
  140. rescue CarrierWave::IntegrityError
  141. return_photo_error I18n.t "photos.create.integrity_error"
  142. rescue RuntimeError
  143. return_photo_error I18n.t "photos.create.runtime_error"
  144. end
  145. 1 def return_photo_error(message)
  146. respond_to do |format|
  147. format.json { render(layout: false, json: {"success" => false, "error" => message}.to_json) }
  148. format.html { render(layout: false, json: {"success" => false, "error" => message}.to_json) }
  149. end
  150. end
  151. 1 def photo_service
  152. 7 @photo_service ||= PhotoService.new(current_user, false)
  153. end
  154. end

app/controllers/poll_participations_controller.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PollParticipationsController < ApplicationController
  3. 1 before_action :authenticate_user!
  4. 1 def create
  5. 5 poll_participation = poll_service.vote(params[:post_id], params[:poll_answer_id])
  6. 2 respond_to do |format|
  7. 2 format.mobile { redirect_to stream_path }
  8. 4 format.json { render json: poll_participation, :status => 201 }
  9. end
  10. rescue ActiveRecord::RecordInvalid
  11. 1 respond_to do |format|
  12. 1 format.mobile { redirect_to stream_path }
  13. 2 format.json { head :forbidden }
  14. end
  15. end
  16. 1 private
  17. 1 def poll_service
  18. 5 @poll_service ||= PollParticipationService.new(current_user)
  19. end
  20. end

app/controllers/posts_controller.rb

100.0% lines covered

42 relevant lines. 42 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class PostsController < ApplicationController
  6. 1 before_action :authenticate_user!, only: %i(destroy mentionable)
  7. 1 before_action :set_format_if_malformed_from_status_net, only: :show
  8. 1 respond_to :html, :mobile, :json
  9. 1 rescue_from Diaspora::NonPublic do
  10. 1 authenticate_user!
  11. end
  12. 1 rescue_from Diaspora::NotMine do
  13. 1 render plain: I18n.t("posts.show.forbidden"), status: 403
  14. end
  15. 1 def show
  16. 14 post = post_service.find!(params[:id])
  17. 12 post_service.mark_user_notifications(post.id)
  18. 12 presenter = PostPresenter.new(post, current_user)
  19. 12 respond_to do |format|
  20. 12 format.html do
  21. 6 gon.post = presenter.with_initial_interactions
  22. 6 render locals: {post: presenter}
  23. end
  24. 17 format.mobile { render locals: {post: post} }
  25. 13 format.json { render json: presenter.with_interactions }
  26. end
  27. end
  28. 1 def oembed
  29. 2 post_id = OEmbedPresenter.id_from_url(params.delete(:url))
  30. 2 post = post_service.find!(post_id)
  31. 1 oembed = params.slice(:format, :maxheight, :minheight)
  32. 1 render json: OEmbedPresenter.new(post, oembed)
  33. rescue
  34. 1 head :not_found
  35. end
  36. 1 def mentionable
  37. 4 respond_to do |format|
  38. 4 format.json {
  39. 3 if params[:id].present? && params[:q].present?
  40. 2 render json: post_service.mentionable_in_comment(params[:id], params[:q])
  41. else
  42. 1 head :no_content
  43. end
  44. }
  45. 5 format.any { head :not_acceptable }
  46. end
  47. rescue ActiveRecord::RecordNotFound
  48. 1 head :not_found
  49. end
  50. 1 def destroy
  51. 4 post_service.destroy(params[:id])
  52. 2 respond_to do |format|
  53. 3 format.json { head :no_content }
  54. 3 format.any { redirect_to stream_path }
  55. end
  56. end
  57. 1 private
  58. 1 def post_service
  59. 37 @post_service ||= PostService.new(current_user)
  60. end
  61. 1 def set_format_if_malformed_from_status_net
  62. 14 request.format = :html if request.format == "application/html+xml"
  63. end
  64. end

app/controllers/profiles_controller.rb

95.12% lines covered

41 relevant lines. 39 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class ProfilesController < ApplicationController
  6. 1 before_action :authenticate_user!, :except => ['show']
  7. 1 respond_to :html, :except => [:show]
  8. 1 respond_to :js, :only => :update
  9. # this is terrible because we're actually serving up the associated person here;
  10. # however, this is the effect that we want for now
  11. 1 def show
  12. 1 @person = Person.find_by_guid!(params[:id])
  13. 1 respond_to do |format|
  14. 2 format.json { render :json => PersonPresenter.new(@person, current_user) }
  15. end
  16. end
  17. 1 def edit
  18. 4 @person = current_user.person
  19. 4 @aspect = :person_edit
  20. 4 @profile = @person.profile
  21. 12 gon.preloads[:tagsArray] = @profile.tags.map {|tag| {name: "##{tag.name}", value: "##{tag.name}"} }
  22. end
  23. 1 def update
  24. # upload and set new profile photo
  25. 11 @profile_attrs = profile_params
  26. 11 munge_tag_string
  27. #checkbox tags wtf
  28. 11 @profile_attrs[:searchable] ||= false
  29. 11 @profile_attrs[:nsfw] ||= false
  30. 11 @profile_attrs[:public_details] ||= false
  31. 11 if params[:photo_id]
  32. @profile_attrs[:photo] = Photo.where(:author_id => current_user.person_id, :id => params[:photo_id]).first
  33. end
  34. 11 if current_user.update_profile(@profile_attrs)
  35. 10 flash[:notice] = I18n.t 'profiles.update.updated'
  36. else
  37. 1 flash[:error] = I18n.t 'profiles.update.failed'
  38. end
  39. 11 respond_to do |format|
  40. 11 format.js { head :ok }
  41. 11 format.any {
  42. 11 if current_user.getting_started?
  43. redirect_to getting_started_path
  44. else
  45. 11 redirect_to edit_profile_path
  46. end
  47. }
  48. end
  49. end
  50. 1 private
  51. 1 def munge_tag_string
  52. 11 unless @profile_attrs[:tag_string].nil? || @profile_attrs[:tag_string] == I18n.t('profiles.edit.your_tags_placeholder')
  53. 3 @profile_attrs[:tag_string].split( " " ).each do |extra_tag|
  54. 2 extra_tag.strip!
  55. 2 unless extra_tag == ""
  56. 2 extra_tag = "##{extra_tag}" unless extra_tag.start_with?( "#" )
  57. 2 params[:tags] += " #{extra_tag}"
  58. end
  59. end
  60. end
  61. 11 @profile_attrs[:tag_string] = (params[:tags]) ? params[:tags].gsub(',',' ') : ""
  62. end
  63. 1 def profile_params
  64. 11 params.require(:profile).permit(:first_name, :last_name, :gender, :bio,
  65. :location, :searchable, :tag_string, :nsfw,
  66. :public_details, date: %i[year month day]).to_h || {}
  67. end
  68. end

app/controllers/registrations_controller.rb

96.43% lines covered

28 relevant lines. 27 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class RegistrationsController < Devise::RegistrationsController
  6. 1 before_action :check_registrations_open_or_valid_invite!, except: :registrations_closed
  7. 10 layout -> { request.format == :mobile ? "application" : "with_header_with_footer" }
  8. 1 def create
  9. 14 @user = User.build(user_params)
  10. 14 if @user.sign_up
  11. 7 flash[:notice] = t("registrations.create.success")
  12. 7 @user.process_invite_acceptence(invite) if invite.present?
  13. 7 @user.seed_aspects
  14. 7 @user.send_welcome_message
  15. 7 sign_in_and_redirect(:user, @user)
  16. 7 logger.info "event=registration status=successful user=#{@user.diaspora_handle}"
  17. else
  18. 7 @user.errors.delete(:person)
  19. 7 flash.now[:error] = @user.errors.full_messages.join(" - ")
  20. 7 logger.info "event=registration status=failure errors='#{@user.errors.full_messages.join(', ')}'"
  21. 7 render action: "new"
  22. end
  23. end
  24. 1 def registrations_closed
  25. render "registrations/registrations_closed"
  26. end
  27. 1 private
  28. 1 def check_registrations_open_or_valid_invite!
  29. 21 return true if AppConfig.settings.enable_registrations? || invite.try(:can_be_used?)
  30. 5 flash[:error] = t("registrations.invalid_invite") if params[:invite]
  31. 5 redirect_to registrations_closed_path
  32. end
  33. 1 def invite
  34. 27 @invite ||= InvitationCode.find_by_token(params[:invite][:token]) if params[:invite].present?
  35. end
  36. 1 helper_method :invite
  37. 1 def user_params
  38. 14 params.require(:user).permit(
  39. :username, :email, :getting_started, :password, :password_confirmation, :language, :disable_mail,
  40. :show_community_spotlight_in_stream, :auto_follow_back, :auto_follow_back_aspect_id,
  41. :remember_me, :captcha, :captcha_key
  42. )
  43. end
  44. end

app/controllers/report_controller.rb

86.36% lines covered

22 relevant lines. 19 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class ReportController < ApplicationController
  6. 1 before_action :authenticate_user!
  7. 1 before_action :redirect_unless_moderator, except: [:create]
  8. 1 def index
  9. 2 @reports = Report.where(reviewed: false)
  10. end
  11. 1 def update
  12. 4 if report = Report.where(id: params[:id]).first
  13. report.mark_as_reviewed
  14. end
  15. 4 redirect_to :action => :index
  16. end
  17. 1 def destroy
  18. 4 if (report = Report.where(id: params[:id]).first) && report.destroy_reported_item
  19. flash[:notice] = I18n.t "report.status.destroyed"
  20. else
  21. 4 flash[:error] = I18n.t "report.status.failed"
  22. end
  23. 4 redirect_to action: :index
  24. end
  25. 1 def create
  26. 2 report = current_user.reports.new(report_params)
  27. 2 if report.save
  28. 2 render json: true, status: 200
  29. else
  30. head :conflict
  31. end
  32. end
  33. 1 private
  34. 1 def report_params
  35. 2 params.require(:report).permit(:item_id, :item_type, :text)
  36. end
  37. end

app/controllers/reshares_controller.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ResharesController < ApplicationController
  3. 1 before_action :authenticate_user!, except: :index
  4. 1 respond_to :json
  5. 1 def create
  6. 5 reshare = reshare_service.create(params[:root_guid])
  7. rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
  8. 1 render plain: I18n.t("reshares.create.error"), status: 422
  9. else
  10. 4 render json: PostPresenter.new(reshare, current_user).with_interactions, status: 201
  11. end
  12. 1 def index
  13. 5 render json: reshare_service.find_for_post(params[:post_id])
  14. .includes(author: :profile)
  15. .as_api_response(:backbone)
  16. end
  17. 1 private
  18. 1 def reshare_service
  19. 10 @reshare_service ||= ReshareService.new(current_user)
  20. end
  21. end

app/controllers/search_controller.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class SearchController < ApplicationController
  3. 1 before_action :authenticate_user!
  4. 1 def search
  5. 4 if search_query.starts_with?('#')
  6. 3 if search_query.length > 1
  7. 2 respond_to do |format|
  8. 2 format.json {redirect_to tags_path(:q => search_query.delete("#."))}
  9. 4 format.any {redirect_to tag_path(:name => search_query.delete("#."))}
  10. end
  11. else
  12. 1 flash[:error] = I18n.t('tags.show.none', :name => search_query)
  13. 1 redirect_back fallback_location: stream_path
  14. end
  15. else
  16. 1 redirect_to people_path(:q => search_query)
  17. end
  18. end
  19. 1 private
  20. 1 def search_query
  21. 13 @search_query ||= (params[:q] || params[:term] || '').strip
  22. end
  23. end

app/controllers/services_controller.rb

90.2% lines covered

51 relevant lines. 46 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class ServicesController < ApplicationController
  6. # We need to take a raw POST from an omniauth provider with no authenticity token.
  7. # See https://github.com/intridea/omniauth/issues/203
  8. # See also http://www.communityguides.eu/articles/16
  9. 1 skip_before_action :verify_authenticity_token, :only => :create
  10. 1 before_action :authenticate_user!
  11. 1 before_action :abort_if_already_authorized, :abort_if_read_only_access, :only => :create
  12. 1 respond_to :html
  13. 1 respond_to :json, :only => :inviter
  14. 1 def index
  15. 1 @services = current_user.services
  16. end
  17. 1 def create
  18. 4 service = Service.initialize_from_omniauth( omniauth_hash )
  19. 4 if current_user.services << service
  20. 4 no_profile_image_before_update = no_profile_image?
  21. 4 current_user.update_profile_with_omniauth(service.info)
  22. 4 fetch_photo(service) if no_profile_image_before_update
  23. 4 flash[:notice] = I18n.t 'services.create.success'
  24. else
  25. flash[:error] = I18n.t 'services.create.failure'
  26. end
  27. 4 redirect_to_origin
  28. end
  29. 1 def failure
  30. logger.info "error in oauth #{params.inspect}"
  31. flash[:error] = t('services.failure.error')
  32. redirect_to services_url
  33. end
  34. 1 def destroy
  35. 1 @service = current_user.services.find(params[:id])
  36. 1 @service.destroy
  37. 1 flash[:notice] = I18n.t 'services.destroy.success'
  38. 1 redirect_to services_url
  39. end
  40. 1 private
  41. 1 def abort_if_already_authorized
  42. 8 if service = Service.where(uid: omniauth_hash['uid']).first
  43. 2 flash[:error] = I18n.t( 'services.create.already_authorized',
  44. diaspora_id: service.user.profile.diaspora_handle,
  45. service_name: service.provider.camelize )
  46. 2 redirect_to_origin
  47. end
  48. end
  49. 1 def abort_if_read_only_access
  50. 6 if omniauth_hash['provider'] == 'twitter' && twitter_access_level == 'read'
  51. 2 flash[:error] = I18n.t( 'services.create.read_only_access' )
  52. 2 redirect_to_origin
  53. end
  54. end
  55. 1 def redirect_to_origin
  56. 8 if origin
  57. 8 redirect_to origin
  58. else
  59. render(text: "<script>window.close()</script>")
  60. end
  61. end
  62. 1 def no_profile_image?
  63. 2 current_user.profile[:image_url].blank?
  64. end
  65. 1 def fetch_photo(service)
  66. 3 Workers::FetchProfilePhoto.perform_async(current_user.id, service.id, service.info["image"])
  67. end
  68. 1 def origin
  69. 16 request.env['omniauth.origin']
  70. end
  71. 1 def omniauth_hash
  72. 20 request.env['omniauth.auth']
  73. end
  74. 1 def twitter_access_token
  75. 2 omniauth_hash['extra']['access_token']
  76. end
  77. #https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema #=> normalized hash
  78. #https://gist.github.com/oliverbarnes/6096959 #=> hash with twitter specific extra
  79. 1 def twitter_access_level
  80. 2 twitter_access_token.response.header['x-access-level']
  81. end
  82. end

app/controllers/sessions_controller.rb

41.67% lines covered

24 relevant lines. 10 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class SessionsController < Devise::SessionsController
  6. # rubocop:disable Rails/LexicallyScopedActionFilter
  7. 1 before_action :authenticate_with_2fa, only: :create
  8. # rubocop:enable Rails/LexicallyScopedActionFilter
  9. 1 def find_user
  10. 3 return User.find_for_authentication(username: params[:user][:username]) if params[:user][:username]
  11. User.find(session[:otp_user_id]) if session[:otp_user_id]
  12. end
  13. 1 def authenticate_with_2fa
  14. 3 self.resource = find_user
  15. 3 return true unless resource&.otp_required_for_login?
  16. if params[:user][:otp_attempt].present? && session[:otp_user_id]
  17. authenticate_with_two_factor_via_otp(resource)
  18. else
  19. strategy = Warden::Strategies[:database_authenticatable].new(warden.env, :user)
  20. prompt_for_two_factor(strategy.user) if strategy.valid? && strategy._run!.successful?
  21. end
  22. end
  23. 1 def valid_otp_attempt?(user)
  24. user.validate_and_consume_otp!(params[:user][:otp_attempt]) ||
  25. user.invalidate_otp_backup_code!(params[:user][:otp_attempt])
  26. rescue OpenSSL::Cipher::CipherError => _error
  27. false
  28. end
  29. 1 def authenticate_with_two_factor_via_otp(user)
  30. if valid_otp_attempt?(user)
  31. session.delete(:otp_user_id)
  32. sign_in(user)
  33. else
  34. flash.now[:alert] = "Invalid token"
  35. prompt_for_two_factor(user)
  36. end
  37. end
  38. 1 def prompt_for_two_factor(user)
  39. session[:otp_user_id] = user.id
  40. render :two_factor
  41. end
  42. end

app/controllers/share_visibilities_controller.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. 1 class ShareVisibilitiesController < ApplicationController
  7. 1 before_action :authenticate_user!
  8. 1 def update
  9. 4 post = post_service.find!(params[:post_id])
  10. 2 current_user.toggle_hidden_shareable(post)
  11. 2 head :ok
  12. end
  13. 1 private
  14. 1 def post_service
  15. 4 @post_service ||= PostService.new(current_user)
  16. end
  17. end

app/controllers/status_messages_controller.rb

88.0% lines covered

50 relevant lines. 44 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class StatusMessagesController < ApplicationController
  6. 1 before_action :authenticate_user!
  7. 1 before_action :remove_getting_started, only: :create
  8. 1 respond_to :html, :mobile, :json
  9. 1 layout "application", only: :bookmarklet
  10. # Called when a user clicks "Mention" on a profile page
  11. # @param person_id [Integer] The id of the person to be mentioned
  12. 1 def new
  13. 3 if params[:person_id] && fetch_person(params[:person_id])
  14. 2 @aspect = :profile
  15. 2 @contact = current_user.contact_for(@person)
  16. 2 if @contact
  17. 2 @aspects_with_person = @contact.aspects.load
  18. 2 render layout: nil
  19. else
  20. @aspects_with_person = []
  21. end
  22. 1 elsif request.format == :mobile
  23. @aspect = :all
  24. @aspects = current_user.aspects.load
  25. else
  26. 1 redirect_to stream_path
  27. end
  28. end
  29. 1 def bookmarklet
  30. 4 @aspects = current_user.aspects
  31. 4 gon.preloads[:bookmarklet] = {
  32. content: params[:content],
  33. title: params[:title],
  34. url: params[:url],
  35. notes: params[:notes]
  36. }
  37. end
  38. 1 def create
  39. 25 status_message = StatusMessageCreationService.new(current_user).create(normalize_params)
  40. 22 respond_to do |format|
  41. 23 format.mobile { redirect_to stream_path }
  42. 35 format.json { render json: PostPresenter.new(status_message, current_user), status: 201 }
  43. end
  44. rescue StatusMessageCreationService::BadAspectsIDs
  45. 1 render status: 422, plain: I18n.t("status_messages.bad_aspects")
  46. rescue StandardError => error
  47. 2 handle_create_error(error)
  48. end
  49. 1 private
  50. 1 def fetch_person(person_id)
  51. 2 @person = Person.where(id: person_id).first
  52. end
  53. 1 def handle_create_error(error)
  54. 2 logger.debug error
  55. 2 respond_to do |format|
  56. 3 format.mobile { redirect_to stream_path }
  57. 3 format.json { render plain: error.message, status: 403 }
  58. end
  59. end
  60. 1 def comes_from_others_profile_page?
  61. coming_from_profile_page? && !own_profile_page?
  62. end
  63. 1 def coming_from_profile_page?
  64. request.env["HTTP_REFERER"].include?("people")
  65. end
  66. 1 def own_profile_page?
  67. request.env["HTTP_REFERER"].include?("/people/" + current_user.guid)
  68. end
  69. 1 def normalize_params
  70. 25 params.permit(
  71. :location_address,
  72. :location_coords,
  73. :poll_question,
  74. status_message: %i[text provider_display_name],
  75. poll_answers: []
  76. ).to_h.merge(
  77. services: [*params[:services]].compact,
  78. aspect_ids: normalize_aspect_ids,
  79. public: [*params[:aspect_ids]].first == "public",
  80. photos: [*params[:photos]].compact
  81. )
  82. end
  83. 1 def normalize_aspect_ids
  84. 25 aspect_ids = [*params[:aspect_ids]]
  85. 25 if aspect_ids.first == "all_aspects"
  86. 2 current_user.aspect_ids
  87. else
  88. 23 aspect_ids
  89. end
  90. end
  91. 1 def remove_getting_started
  92. 26 current_user.disable_getting_started
  93. end
  94. end

app/controllers/streams_controller.rb

88.37% lines covered

43 relevant lines. 38 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class StreamsController < ApplicationController
  6. 1 before_action :authenticate_user!, except: :public
  7. 1 before_action :save_selected_aspects, :only => :aspects
  8. 23 layout proc { request.format == :mobile ? "application" : "with_header" }
  9. 1 respond_to :html,
  10. :mobile,
  11. :json
  12. 1 def aspects
  13. 12 aspect_ids = (session[:a_ids] || [])
  14. 12 @stream = Stream::Aspect.new(current_user, aspect_ids,
  15. :max_time => max_time)
  16. 12 stream_responder
  17. end
  18. 1 def public
  19. 3 stream_responder(Stream::Public)
  20. end
  21. 1 def local_public
  22. if AppConfig.local_posts_stream?(current_user)
  23. stream_responder(Stream::LocalPublic)
  24. else
  25. head :not_found
  26. end
  27. end
  28. 1 def activity
  29. 1 stream_responder(Stream::Activity)
  30. end
  31. 1 def multi
  32. 4 if current_user.getting_started
  33. 1 gon.preloads[:getting_started] = true
  34. 1 inviter = current_user.invited_by.try(:person)
  35. 1 gon.preloads[:mentioned_person] = {name: inviter.name, handle: inviter.diaspora_handle} if inviter
  36. end
  37. 4 stream_responder(Stream::Multi)
  38. end
  39. 1 def commented
  40. stream_responder(Stream::Comments)
  41. end
  42. 1 def liked
  43. 1 stream_responder(Stream::Likes)
  44. end
  45. 1 def mentioned
  46. 1 stream_responder(Stream::Mention)
  47. end
  48. 1 def followed_tags
  49. 1 gon.preloads[:tagFollowings] = tags
  50. 1 stream_responder(Stream::FollowedTag)
  51. end
  52. 1 private
  53. 1 def stream_responder(stream_klass=nil)
  54. 23 if stream_klass.present?
  55. 11 @stream ||= stream_klass.new(current_user, :max_time => max_time)
  56. end
  57. 23 respond_with do |format|
  58. 40 format.html { render 'streams/main_stream' }
  59. 28 format.mobile { render 'streams/main_stream' }
  60. 39 format.json { render :json => @stream.stream_posts.map {|p| LastThreeCommentsDecorator.new(PostPresenter.new(p, current_user)) }}
  61. end
  62. end
  63. 1 def save_selected_aspects
  64. 12 if params[:a_ids].present?
  65. session[:a_ids] = params[:a_ids]
  66. end
  67. end
  68. end

app/controllers/tag_followings_controller.rb

87.5% lines covered

24 relevant lines. 21 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. 1 class TagFollowingsController < ApplicationController
  7. 1 before_action :authenticate_user!
  8. 1 respond_to :json
  9. 1 respond_to :html, only: [:manage]
  10. # POST /tag_followings
  11. # POST /tag_followings.xml
  12. 1 def create
  13. 9 tag = tag_followings_service.create(params["name"])
  14. 6 render json: tag.to_json, status: :created
  15. rescue TagFollowingService::DuplicateTag
  16. render json: tag_followings_service.find(params["name"]), status: :created
  17. rescue StandardError
  18. 3 head :forbidden
  19. end
  20. # DELETE /tag_followings/1
  21. # DELETE /tag_followings/1.xml
  22. 1 def destroy
  23. 1 tag_followings_service.destroy(params["id"])
  24. 1 respond_to do |format|
  25. 2 format.any(:js, :json) { head :no_content }
  26. end
  27. rescue ActiveRecord::RecordNotFound
  28. respond_to do |format|
  29. format.any(:js, :json) { head :forbidden }
  30. end
  31. end
  32. 1 def index
  33. 1 respond_to do |format|
  34. 2 format.json{ render(:json => tags.to_json, :status => 200) }
  35. end
  36. end
  37. 1 def manage
  38. 2 redirect_to followed_tags_stream_path unless request.format == :mobile
  39. 2 gon.preloads[:tagFollowings] = tags
  40. end
  41. 1 private
  42. 1 def tag_followings_service
  43. 10 TagFollowingService.new(current_user)
  44. end
  45. end

app/controllers/tags_controller.rb

100.0% lines covered

41 relevant lines. 41 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class TagsController < ApplicationController
  6. 1 before_action :ensure_page, :only => :show
  7. 1 helper_method :tag_followed?
  8. 11 layout proc { request.format == :mobile ? "application" : "with_header" }, only: :show
  9. 1 respond_to :html, :only => [:show]
  10. 1 respond_to :json, :only => [:index, :show]
  11. 1 def index
  12. 4 if params[:q] && params[:q].length > 1
  13. 1 params[:q].gsub!("#", "")
  14. 1 params[:limit] = !params[:limit].blank? ? params[:limit].to_i : 10
  15. 1 @tags = ActsAsTaggableOn::Tag.autocomplete(params[:q]).limit(params[:limit] - 1)
  16. 1 prep_tags_for_javascript
  17. 1 respond_to do |format|
  18. 2 format.json{ render(:json => @tags.to_json, :status => 200) }
  19. end
  20. else
  21. 3 respond_to do |format|
  22. 5 format.json { head :unprocessable_entity }
  23. 4 format.html { redirect_to tag_path("partytimeexcellent") }
  24. end
  25. end
  26. end
  27. 1 def show
  28. 13 redirect_to(:action => :show, :name => downcased_tag_name) && return if tag_has_capitals?
  29. 12 if user_signed_in?
  30. 4 gon.preloads[:tagFollowings] = tags
  31. end
  32. 12 stream = Stream::Tag.new(current_user, params[:name], max_time: max_time, page: params[:page])
  33. 12 @stream = TagStreamPresenter.new(stream)
  34. 12 respond_with do |format|
  35. 12 format.json do
  36. 2 posts = stream.stream_posts.map do |p|
  37. 2 LastThreeCommentsDecorator.new(PostPresenter.new(p, current_user))
  38. end
  39. 2 render json: posts
  40. end
  41. end
  42. end
  43. 1 private
  44. 1 def tag_followed?
  45. 2 TagFollowing.user_is_following?(current_user, params[:name])
  46. end
  47. 1 def tag_has_capitals?
  48. 13 mb_tag = params[:name].mb_chars
  49. 13 mb_tag.downcase != mb_tag
  50. end
  51. 1 def downcased_tag_name
  52. 1 params[:name].mb_chars.downcase.to_s
  53. end
  54. 1 def prep_tags_for_javascript
  55. 1 @tags = @tags.map {|tag|
  56. 1 { :name => ("#" + tag.name) }
  57. }
  58. 1 @tags << { :name => ('#' + params[:q]) }
  59. 1 @tags.uniq!
  60. end
  61. end

app/controllers/terms_controller.rb

85.71% lines covered

7 relevant lines. 6 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class TermsController < ApplicationController
  6. 1 respond_to :html, :mobile
  7. 1 def index
  8. 2 partial_dir = Rails.root.join('app', 'views', 'terms')
  9. 2 if partial_dir.join('terms.haml').exist? ||
  10. partial_dir.join('terms.erb').exist?
  11. render :terms
  12. else
  13. 2 render :default
  14. end
  15. end
  16. end

app/controllers/two_factor_authentications_controller.rb

100.0% lines covered

32 relevant lines. 32 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class TwoFactorAuthenticationsController < ApplicationController
  3. 1 before_action :authenticate_user!
  4. 1 before_action :verify_otp_required, only: [:create]
  5. 1 def show
  6. 2 @user = current_user
  7. end
  8. 1 def create
  9. 1 current_user.otp_secret = User.generate_otp_secret(32)
  10. 1 current_user.save!
  11. 1 redirect_to confirm_two_factor_authentication_path
  12. end
  13. 1 def confirm_2fa
  14. 2 redirect_to two_factor_authentication_path if current_user.otp_required_for_login?
  15. end
  16. 1 def confirm_and_activate_2fa
  17. 2 if current_user.validate_and_consume_otp!(params[:user][:code])
  18. 1 current_user.otp_required_for_login = true
  19. 1 current_user.save!
  20. 1 flash[:notice] = t("two_factor_auth.flash.success_activation")
  21. 1 redirect_to recovery_codes_two_factor_authentication_path
  22. else
  23. 1 flash[:alert] = t("two_factor_auth.flash.error_token")
  24. 1 redirect_to confirm_two_factor_authentication_path
  25. end
  26. end
  27. 1 def recovery_codes
  28. 1 @recovery_codes = current_user.generate_otp_backup_codes!
  29. 1 current_user.save!
  30. end
  31. 1 def destroy
  32. 2 if current_user.valid_password?(params[:two_factor_authentication][:password])
  33. 1 current_user.otp_required_for_login = false
  34. 1 current_user.save!
  35. 1 flash[:notice] = t("two_factor_auth.flash.success_deactivation")
  36. else
  37. 1 flash[:alert] = t("users.destroy.wrong_password")
  38. end
  39. 2 redirect_to two_factor_authentication_path
  40. end
  41. 1 private
  42. 1 def verify_otp_required
  43. 1 redirect_to two_factor_authentication_path if current_user.otp_required_for_login?
  44. end
  45. end

app/controllers/users_controller.rb

81.34% lines covered

134 relevant lines. 109 lines covered and 25 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class UsersController < ApplicationController
  6. 1 before_action :authenticate_user!, except: %i(new create public)
  7. 1 respond_to :html
  8. 1 def edit
  9. 4 @user = current_user
  10. 4 set_email_preferences
  11. end
  12. 1 def privacy_settings
  13. 1 @blocks = current_user.blocks.includes(:person)
  14. end
  15. 1 def update
  16. 38 @user = current_user
  17. 38 if params[:change_password] && user_password_params
  18. 1 password_changed = change_password(user_password_params)
  19. 1 return redirect_to new_user_session_path if password_changed
  20. 37 elsif user_params
  21. 37 update_user(user_params)
  22. end
  23. 38 set_email_preferences
  24. 38 render :edit
  25. end
  26. 1 def destroy
  27. 3 if params[:user] && params[:user][:current_password] && current_user.valid_password?(params[:user][:current_password])
  28. 2 current_user.close_account!
  29. 2 sign_out current_user
  30. 2 redirect_to(new_user_session_path(format: request[:format]), notice: I18n.t("users.destroy.success"))
  31. else
  32. 1 if params[:user].present? && params[:user][:current_password].present?
  33. 1 flash[:error] = t "users.destroy.wrong_password"
  34. else
  35. flash[:error] = t "users.destroy.no_password"
  36. end
  37. 1 redirect_back fallback_location: edit_user_path
  38. end
  39. end
  40. 1 def public
  41. 8 if @user = User.find_by_username(params[:username])
  42. 8 respond_to do |format|
  43. 8 format.atom do
  44. 6 @posts = Post.where(author_id: @user.person_id, public: true)
  45. .order("created_at DESC")
  46. .limit(25)
  47. 6 .map {|post| post.is_a?(Reshare) ? post.absolute_root : post }
  48. .compact
  49. end
  50. 10 format.any { redirect_to person_path(@user.person) }
  51. end
  52. else
  53. redirect_to stream_path, error: I18n.t("users.public.does_not_exist", username: params[:username])
  54. end
  55. end
  56. 1 def getting_started
  57. 5 @user = current_user
  58. 5 @person = @user.person
  59. 5 @profile = @user.profile
  60. 5 gon.preloads[:inviter] = PersonPresenter.new(current_user.invited_by.try(:person), current_user).as_json
  61. 5 gon.preloads[:tagsArray] = current_user.followed_tags.map {|tag| {name: "##{tag.name}", value: "##{tag.name}"} }
  62. 5 render "users/getting_started"
  63. end
  64. 1 def getting_started_completed
  65. user = current_user
  66. user.getting_started = false
  67. user.save
  68. redirect_to stream_path
  69. end
  70. 1 def export_profile
  71. 1 current_user.queue_export
  72. 1 flash[:notice] = I18n.t("users.edit.export_in_progress")
  73. 1 redirect_to edit_user_path
  74. end
  75. 1 def download_profile
  76. 1 redirect_to current_user.export.url
  77. end
  78. 1 def export_photos
  79. 1 current_user.queue_export_photos
  80. 1 flash[:notice] = I18n.t("users.edit.export_photos_in_progress")
  81. 1 redirect_to edit_user_path
  82. end
  83. 1 def download_photos
  84. 1 redirect_to current_user.exported_photos_file.url
  85. end
  86. 1 def confirm_email
  87. 3 if current_user.confirm_email(params[:token])
  88. 2 flash[:notice] = I18n.t("users.confirm_email.email_confirmed", email: current_user.email)
  89. 1 elsif current_user.unconfirmed_email.present?
  90. 1 flash[:error] = I18n.t("users.confirm_email.email_not_confirmed")
  91. end
  92. 3 redirect_to edit_user_path
  93. end
  94. 1 private
  95. 1 def user_params
  96. 74 params.fetch(:user).permit(
  97. :email,
  98. :language,
  99. :color_theme,
  100. :disable_mail,
  101. :show_community_spotlight_in_stream,
  102. :auto_follow_back,
  103. :auto_follow_back_aspect_id,
  104. :getting_started,
  105. :post_default_public,
  106. :exported_photos_file,
  107. :export,
  108. email_preferences: UserPreference::VALID_EMAIL_TYPES.map(&:to_sym)
  109. )
  110. end
  111. 1 def user_password_params
  112. 2 params.fetch(:user).permit(
  113. :current_password,
  114. :password,
  115. :password_confirmation
  116. )
  117. end
  118. 1 def update_user(user_data)
  119. 37 if user_data[:email_preferences]
  120. 22 change_email_preferences(user_data)
  121. 15 elsif user_data[:language]
  122. 3 change_language(user_data)
  123. 12 elsif user_data[:email]
  124. 7 change_email(user_data)
  125. 5 elsif user_data[:auto_follow_back]
  126. change_settings(user_data, "users.update.follow_settings_changed", "users.update.follow_settings_not_changed")
  127. 5 elsif user_data[:post_default_public]
  128. change_post_default(user_data)
  129. 5 elsif user_data[:color_theme]
  130. 1 change_settings(user_data, "users.update.color_theme_changed", "users.update.color_theme_not_changed")
  131. 4 elsif user_data[:export] || user_data[:exported_photos_file]
  132. upload_export_files(user_data)
  133. else
  134. 4 change_settings(user_data)
  135. end
  136. end
  137. 1 def change_password(password_params)
  138. 1 if @user.update_with_password(password_params)
  139. flash[:notice] = t("users.update.password_changed")
  140. true
  141. else
  142. 1 flash.now[:error] = t("users.update.password_not_changed")
  143. 1 false
  144. end
  145. end
  146. 1 def change_post_default(user_data)
  147. # by default user_data[:post_default_public] is set to false
  148. case params[:aspect_ids].try(:first)
  149. when "public"
  150. user_data[:post_default_public] = true
  151. when "all_aspects"
  152. params[:aspect_ids] = @user.aspects.map {|a| a.id.to_s }
  153. end
  154. @user.update_post_default_aspects params[:aspect_ids].to_a
  155. change_settings(user_data)
  156. end
  157. # change email notifications
  158. 1 def change_email_preferences(user_data)
  159. 22 @user.update_user_preferences(user_data[:email_preferences])
  160. 22 flash.now[:notice] = t("users.update.email_notifications_changed")
  161. end
  162. 1 def change_language(user_data)
  163. 3 if @user.update(user_data)
  164. 2 I18n.locale = @user.language
  165. 2 flash.now[:notice] = t("users.update.language_changed")
  166. else
  167. 1 flash.now[:error] = t("users.update.language_not_changed")
  168. end
  169. end
  170. 1 def change_email(user_data)
  171. 7 if AppConfig.mail.enable?
  172. 6 @user.unconfirmed_email = user_data[:email]
  173. 6 if @user.save
  174. 5 @user.send_confirm_email
  175. 5 flash.now[:notice] = t("users.update.unconfirmed_email_changed")
  176. else
  177. 1 @user.reload # match user object with the database
  178. 1 flash.now[:error] = t("users.update.unconfirmed_email_not_changed")
  179. end
  180. else
  181. 1 @user.email = user_data[:email]
  182. 1 if @user.save
  183. 1 flash.now[:notice] = t("users.update.settings_updated")
  184. else
  185. @user.reload
  186. flash.now[:error] = t("users.update.unconfirmed_email_not_changed")
  187. end
  188. end
  189. end
  190. 1 def upload_export_files(user_data)
  191. logger.info "Start importing account"
  192. @user.export = user_data[:export] if user_data[:export]
  193. @user.exported_photos_file = user_data[:exported_photos_file] if user_data[:exported_photos_file]
  194. if @user.save
  195. flash.now[:notice] = "Your account migration has been scheduled"
  196. else
  197. flash.now[:error] = "Your account migration could not be scheduled for the following reason:"\
  198. " #{@user.errors.full_messages}"
  199. end
  200. Workers::ImportUser.perform_async(@user.id)
  201. end
  202. 1 def change_settings(user_data, successful="users.update.settings_updated", error="users.update.settings_not_updated")
  203. 5 if @user.update(user_data)
  204. 4 flash.now[:notice] = t(successful)
  205. else
  206. 1 flash.now[:error] = t(error)
  207. end
  208. end
  209. 1 def set_email_preferences
  210. 42 @email_prefs = Hash.new(true)
  211. 42 @user.user_preferences.each do |pref|
  212. 23 @email_prefs[pref.email_type] = false
  213. end
  214. end
  215. end

app/helpers/activity_streams_helper.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. module ActivityStreamsHelper
  3. def add_activitystreams_author(target, person)
  4. target.author do |author|
  5. author.name person.name
  6. author.uri local_or_remote_person_path(person, absolute: true)
  7. author.tag! "activity:object-type", "http://activitystrea.ms/schema/1.0/person"
  8. author.tag! "poco:preferredUsername", person.username
  9. author.tag! "poco:displayName", person.name
  10. end
  11. end
  12. end

app/helpers/application_helper.rb

0.0% lines covered

66 relevant lines. 0 lines covered and 66 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module ApplicationHelper
  6. def pod_name
  7. AppConfig.settings.pod_name
  8. end
  9. def pod_version
  10. AppConfig.version.number
  11. end
  12. def uri_with_username
  13. if user_signed_in?
  14. AppConfig.pod_uri + "?username=#{current_user.username}"
  15. else
  16. AppConfig.pod_uri
  17. end
  18. end
  19. def changelog_url
  20. return AppConfig.settings.changelog_url.get if AppConfig.settings.changelog_url.present?
  21. url = "https://github.com/diaspora/diaspora/blob/master/Changelog.md"
  22. return url if AppConfig.git_revision.blank?
  23. url.sub("/master/", "/#{AppConfig.git_revision}/")
  24. end
  25. def source_url
  26. AppConfig.settings.source_url.presence || "#{root_path.chomp('/')}/source.tar.gz"
  27. end
  28. def donations_enabled?
  29. AppConfig.settings.paypal_donations.enable? ||
  30. AppConfig.settings.liberapay_username.present? ||
  31. AppConfig.bitcoin_donation_address.present?
  32. end
  33. def timeago(time, options={})
  34. timeago_tag(time, options.merge(:class => 'timeago', :title => time.iso8601, :force => true)) if time
  35. end
  36. def bookmarklet_code(height=400, width=620)
  37. "javascript:" +
  38. BookmarkletRenderer.body +
  39. "bookmarklet('#{bookmarklet_url}', #{width}, #{height});"
  40. end
  41. def all_services_connected?
  42. current_user.services.size == AppConfig.configured_services.size
  43. end
  44. def service_unconnected?(service)
  45. AppConfig.show_service?(service, current_user) && current_user.services.none? {|x| x.provider == service }
  46. end
  47. def popover_with_close_html(without_close_html)
  48. without_close_html + link_to('&times;'.html_safe, "#", :class => 'close')
  49. end
  50. # Require jQuery from CDN if possible, falling back to vendored copy, and require
  51. # vendored jquery_ujs
  52. def jquery_include_tag
  53. buf = []
  54. if AppConfig.privacy.jquery_cdn?
  55. version = Jquery::Rails::JQUERY_3_VERSION
  56. buf << [javascript_include_tag("//code.jquery.com/jquery-#{version}.min.js")]
  57. buf << [
  58. nonced_javascript_tag("!window.jQuery && document.write(unescape('#{j javascript_include_tag('jquery3')}'));")
  59. ]
  60. else
  61. buf << [javascript_include_tag("jquery3")]
  62. end
  63. buf << [javascript_include_tag("jquery_ujs")]
  64. buf << [nonced_javascript_tag("jQuery.ajaxSetup({'cache': false});")]
  65. buf << [nonced_javascript_tag("$.fx.off = true;")] if Rails.env.test?
  66. buf.join("\n").html_safe
  67. end
  68. def qrcode_uri
  69. label = current_user.username
  70. current_user.otp_provisioning_uri(label, issuer: AppConfig.environment.url)
  71. end
  72. end

app/helpers/aspect_global_helper.rb

0.0% lines covered

21 relevant lines. 0 lines covered and 21 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module AspectGlobalHelper
  6. def aspect_options_for_select(aspects)
  7. options = {}
  8. aspects.each do |aspect|
  9. options[aspect.to_s] = aspect.id
  10. end
  11. options
  12. end
  13. def publisher_aspects_for(stream)
  14. if stream
  15. aspects = stream.aspects
  16. aspect = stream.aspect
  17. elsif current_user
  18. aspects = current_user.post_default_aspects
  19. aspect = aspects.first
  20. else
  21. return {}
  22. end
  23. {selected_aspects: aspects, aspect: aspect}
  24. end
  25. end

app/helpers/contacts_helper.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. module ContactsHelper
  3. def start_a_conversation_link(aspect, contacts_size)
  4. conv_opts = { class: "conversation_button contacts_button"}
  5. content_tag :span, conv_opts do
  6. content_tag :i,
  7. nil,
  8. class: "entypo-mail contacts-header-icon",
  9. title: t("contacts.index.start_a_conversation")
  10. end
  11. end
  12. end

app/helpers/conversations_helper.rb

0.0% lines covered

7 relevant lines. 0 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. module ConversationsHelper
  3. def conversation_class(conversation, unread_count, selected_conversation_id)
  4. conv_class = unread_count > 0 ? "unread" : ""
  5. return conv_class unless selected_conversation_id && conversation.id == selected_conversation_id
  6. "#{conv_class} selected"
  7. end
  8. end

app/helpers/error_messages_helper.rb

0.0% lines covered

19 relevant lines. 0 lines covered and 19 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module ErrorMessagesHelper
  6. # Render error messages for the given objects. The :message and :header_message options are allowed.
  7. def error_messages_for(*objects)
  8. options = objects.extract_options!
  9. options[:message] ||= I18n.t('error_messages.helper.correct_the_following_errors_and_try_again')
  10. messages = objects.compact.map { |o| o.errors.full_messages }.flatten
  11. unless messages.empty?
  12. content_tag(:div, class: "text-danger") do
  13. list_items = messages.map { |msg| content_tag(:li, msg) }
  14. content_tag(:h2, options[:header_message]) + content_tag(:p, options[:message]) + content_tag(:ul, list_items.join.html_safe)
  15. end
  16. end
  17. end
  18. module FormBuilderAdditions
  19. def error_messages(options = {})
  20. @template.error_messages_for(@object, options)
  21. end
  22. end
  23. end
  24. ActionView::Helpers::FormBuilder.send(:include, ErrorMessagesHelper::FormBuilderAdditions)

app/helpers/getting_started_helper.rb

0.0% lines covered

5 relevant lines. 0 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module GettingStartedHelper
  6. # @return [Boolean] The user has completed all steps in getting started
  7. def has_completed_getting_started?
  8. current_user.getting_started == false
  9. end
  10. end

app/helpers/gon_helper.rb

0.0% lines covered

8 relevant lines. 0 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. module GonHelper
  3. def gon_load_contact(contact)
  4. Gon.preloads[:contacts] ||= []
  5. if Gon.preloads[:contacts].none? {|stored_contact| stored_contact[:person][:id] == contact.person_id }
  6. Gon.preloads[:contacts] << ContactPresenter.new(contact, current_user).full_hash_with_person
  7. end
  8. end
  9. end

app/helpers/interim_stream_hackiness_helper.rb

0.0% lines covered

18 relevant lines. 0 lines covered and 18 lines missed.
    
  1. # frozen_string_literal: true
  2. module InterimStreamHackinessHelper
  3. ##### These methods need to go away once we pass publisher object into the partial ######
  4. def publisher_formatted_text
  5. if params[:prefill].present?
  6. params[:prefill]
  7. elsif defined?(@stream)
  8. @stream.publisher.prefill
  9. else
  10. nil
  11. end
  12. end
  13. def from_group(post)
  14. if defined?(@stream) && params[:controller] == 'multis'
  15. @stream.post_from_group(post)
  16. else
  17. []
  18. end
  19. end
  20. end

app/helpers/invitation_codes_helper.rb

0.0% lines covered

17 relevant lines. 0 lines covered and 17 lines missed.
    
  1. # frozen_string_literal: true
  2. module InvitationCodesHelper
  3. def invite_hidden_tag(invite)
  4. if invite.present?
  5. hidden_field_tag 'invite[token]', invite.token
  6. end
  7. end
  8. def invite_link(invite_code)
  9. text_field_tag :invite_code, invite_code_url(invite_code), class: "form-control", readonly: true
  10. end
  11. def invited_by_message
  12. inviter = current_user.invited_by
  13. if inviter.present?
  14. @person = inviter.person
  15. render partial: "people/add_contact"
  16. end
  17. end
  18. end

app/helpers/language_helper.rb

0.0% lines covered

24 relevant lines. 0 lines covered and 24 lines missed.
    
  1. # frozen_string_literal: true
  2. module LanguageHelper
  3. include ApplicationHelper
  4. def available_language_options
  5. options = []
  6. AVAILABLE_LANGUAGES.each do |locale, language|
  7. options << [language, locale]
  8. end
  9. options.sort_by { |o| o[0] }
  10. end
  11. def get_javascript_strings_for(language, section)
  12. translations = I18n.t(section, locale: DEFAULT_LANGUAGE).dup
  13. translations.deep_merge!(I18n.t(section, locale: language)) if language != DEFAULT_LANGUAGE
  14. translations["pluralization_rule"] = I18n.t("i18n.plural.js_rule", locale: language)
  15. translations["pod_name"] = pod_name
  16. translations
  17. end
  18. def direction_for(string)
  19. return '' unless string.respond_to?(:cleaned_is_rtl?)
  20. string.cleaned_is_rtl? ? 'rtl' : 'ltr'
  21. end
  22. def rtl?
  23. @rtl ||= RTL_LANGUAGES.include?(I18n.locale.to_s)
  24. end
  25. end

app/helpers/layout_helper.rb

0.0% lines covered

44 relevant lines. 0 lines covered and 44 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. # These helper methods can be called in your template to set variables to be used in the layout
  6. # This module should be included in all views globally,
  7. # to do so you may need to add this line to your ApplicationController
  8. # helper :layout
  9. module LayoutHelper
  10. include ApplicationHelper
  11. def title(page_title, show_title = true)
  12. content_for(:title) { page_title.to_s }
  13. @show_title = show_title
  14. end
  15. def page_title(text=nil)
  16. return text unless text.blank?
  17. pod_name
  18. end
  19. def load_javascript_locales(section = 'javascripts')
  20. nonced_javascript_tag do
  21. <<-JS.html_safe
  22. Diaspora.I18n.load(#{get_javascript_strings_for(I18n.locale, section).to_json},
  23. "#{I18n.locale}",
  24. #{get_javascript_strings_for(DEFAULT_LANGUAGE, section).to_json});
  25. Diaspora.Page = "#{params[:controller].camelcase}#{params[:action].camelcase}";
  26. JS
  27. end
  28. end
  29. def current_user_atom_tag
  30. return unless @person.present?
  31. content_tag(:link, "", rel: "alternate", href: @person.atom_url, type: "application/atom+xml",
  32. title: t("layouts.application.public_feed", name: @person.name))
  33. end
  34. def translation_missing_warnings
  35. return if Rails.env == "production"
  36. content_tag(:style) do
  37. <<-CSS
  38. .translation_missing { color: purple; background-color: red; }
  39. CSS
  40. end
  41. end
  42. def include_color_theme(view="desktop")
  43. stylesheet_link_tag "#{current_color_theme}/#{view}", media: "all"
  44. end
  45. def flash_messages
  46. flash.map do |name, msg|
  47. klass = flash_class name
  48. content_tag(:div, msg, class: "flash-body expose") do
  49. content_tag(:div, msg, class: "flash-message message alert alert-#{klass}", role: "alert")
  50. end
  51. end.join(' ').html_safe
  52. end
  53. end

app/helpers/meta_data_helper.rb

0.0% lines covered

42 relevant lines. 0 lines covered and 42 lines missed.
    
  1. # frozen_string_literal: true
  2. module MetaDataHelper
  3. include ActionView::Helpers::AssetUrlHelper
  4. include ActionView::Helpers::TagHelper
  5. def og_prefix
  6. "og: https://ogp.me/ns# article: https://ogp.me/ns/article# profile: https://ogp.me/ns/profile#"
  7. end
  8. def site_url
  9. AppConfig.environment.url
  10. end
  11. def default_image_url
  12. asset_url("assets/branding/logos/asterisk.png", skip_pipeline: true)
  13. end
  14. def default_author_name
  15. AppConfig.settings.pod_name
  16. end
  17. def default_description
  18. AppConfig.settings.default_metas.description
  19. end
  20. def default_title
  21. AppConfig.settings.default_metas.title
  22. end
  23. def general_metas
  24. {
  25. description: {name: "description", content: default_description},
  26. og_description: {property: "description", content: default_description},
  27. og_site_name: {property: "og:site_name", content: default_title},
  28. og_url: {property: "og:url", content: site_url},
  29. og_image: {property: "og:image", content: default_image_url},
  30. og_type: {property: "og:type", content: "website"}
  31. }
  32. end
  33. def metas_tags(attributes_list={}, with_general_metas=true)
  34. attributes_list = general_metas.merge(attributes_list) if with_general_metas
  35. attributes_list.map {|_, attributes| meta_tag attributes }.join("\n").html_safe
  36. end
  37. # recursively calls itself if attribute[:content] is an array
  38. # (metas such as og:image or og:tag can be present multiple times with different values)
  39. def meta_tag(attributes)
  40. return "" if attributes.empty?
  41. return tag(:meta, attributes) unless attributes[:content].respond_to?(:to_ary)
  42. items = attributes.delete(:content)
  43. items.map {|item| meta_tag(attributes.merge(content: item)) }.join("\n")
  44. end
  45. end

app/helpers/mobile_helper.rb

0.0% lines covered

83 relevant lines. 0 lines covered and 83 lines missed.
    
  1. # frozen_string_literal: true
  2. module MobileHelper
  3. def mobile_reshare_icon(post)
  4. if (post.public? || reshare?(post)) && (user_signed_in? && post.author != current_user.person)
  5. absolute_root = reshare?(post) ? post.absolute_root : post
  6. if absolute_root && absolute_root.author != current_user.person
  7. reshare = Reshare.where(author_id: current_user.person_id,
  8. root_guid: absolute_root.guid).first
  9. klass = reshare.present? ? "active" : "inactive"
  10. link_to content_tag(:span, post.reshares.size, class: "count reshare-count"),
  11. reshares_path(root_guid: absolute_root.guid),
  12. title: t("reshares.reshare.reshare_confirmation", author: absolute_root.author_name),
  13. class: "entypo-reshare reshare-action #{klass}"
  14. else
  15. content_tag :div,
  16. content_tag(:span, post.reshares.size, class: "count reshare-count"),
  17. class: "entypo-reshare reshare-action disabled"
  18. end
  19. else
  20. content_tag :div,
  21. content_tag(:span, post.reshares.size, class: "count reshare-count"),
  22. class: "entypo-reshare reshare-action disabled"
  23. end
  24. end
  25. def mobile_like_icon(post)
  26. if current_user&.liked?(post)
  27. link_to content_tag(:span, post.likes.size, class: "count like-count"),
  28. "#",
  29. data: {url: post_like_path(post.id, current_user.like_for(post).id)},
  30. class: "entypo-heart like-action active"
  31. else
  32. link_to content_tag(:span, post.likes.size, class: "count like-count"),
  33. "#",
  34. data: {url: post_likes_path(post.id)},
  35. class: "entypo-heart like-action inactive"
  36. end
  37. end
  38. def mobile_like_comment_icon(comment)
  39. if current_user&.liked?(comment)
  40. link_to content_tag(:span, comment.likes.size, class: "count like-count"),
  41. "#",
  42. data: {url: comment_like_path(comment.id, current_user.like_for(comment).id)},
  43. class: "entypo-heart like-action active"
  44. else
  45. link_to content_tag(:span, comment.likes.size, class: "count like-count"),
  46. "#",
  47. data: {url: comment_likes_path(comment.id)},
  48. class: "entypo-heart like-action inactive"
  49. end
  50. end
  51. def mobile_comment_icon(post)
  52. link_to content_tag(:span, post.comments.size, class: "count comment-count"),
  53. new_post_comment_path(post),
  54. class: "entypo-comment comment-action inactive"
  55. end
  56. def show_comments_link(post, klass="")
  57. if klass == "active"
  58. entypo_class = "entypo-chevron-up"
  59. else
  60. entypo_class = "entypo-chevron-down"
  61. end
  62. link_to safe_join([
  63. t("admins.stats.comments", count: post.comments_count),
  64. content_tag(:i, nil, class: entypo_class)
  65. ]),
  66. post_comments_path(post, format: "mobile"),
  67. class: "show-comments #{klass}"
  68. end
  69. def additional_photos
  70. if photo.status_message_guid?
  71. @additional_photos ||= photo.status_message.photos.order(:created_at)
  72. end
  73. end
  74. def next_photo
  75. @next_photo ||= additional_photos[additional_photos.index(photo)+1]
  76. @next_photo ||= additional_photos.first
  77. end
  78. def previous_photo
  79. @previous_photo ||= additional_photos[additional_photos.index(photo)-1]
  80. end
  81. def photo
  82. @photo ||= current_user.find_visible_shareable_by_id(Photo, params[:id])
  83. end
  84. end

app/helpers/notifications_helper.rb

0.0% lines covered

97 relevant lines. 0 lines covered and 97 lines missed.
    
  1. # frozen_string_literal: true
  2. module NotificationsHelper
  3. include PeopleHelper
  4. include PostsHelper
  5. def object_link(note, actors_html)
  6. target_type = note.popup_translation_key
  7. opts = {actors: actors_html, count: note.actors.size}
  8. if note.respond_to?(:linked_object)
  9. if note.linked_object.nil? && note.respond_to?(:deleted_translation_key)
  10. target_type = note.deleted_translation_key
  11. elsif note.is_a?(Notifications::Mentioned)
  12. opts.merge!(opts_for_mentioned(note.linked_object))
  13. elsif %w(Notifications::CommentOnPost Notifications::AlsoCommented Notifications::Reshared Notifications::Liked)
  14. .include?(note.type)
  15. opts.merge!(opts_for_post(note.linked_object))
  16. elsif note.is_a?(Notifications::LikedComment)
  17. opts.merge!(opts_for_comment(note.linked_object))
  18. elsif note.is_a?(Notifications::ContactsBirthday)
  19. opts.merge!(opts_for_birthday(note))
  20. end
  21. end
  22. translation(target_type, **opts)
  23. end
  24. def translation(target_type, **kwargs)
  25. t(target_type, **kwargs).html_safe # rubocop:disable Rails/OutputSafety
  26. end
  27. def opts_for_post(post)
  28. {
  29. post_author: html_escape(post.author_name),
  30. post_link: link_to(post_page_title(post),
  31. post_path(post),
  32. data: {ref: post.id},
  33. class: "hard_object_link")
  34. }
  35. end
  36. def opts_for_comment(comment)
  37. {
  38. comment_link: link_to(comment.message.title,
  39. post_path(comment.post, anchor: comment.guid),
  40. data: {ref: comment.id},
  41. class: "hard_object_link")
  42. }
  43. end
  44. def opts_for_mentioned(mentioned)
  45. post = mentioned.instance_of?(Comment) ? mentioned.parent : mentioned
  46. {
  47. post_link: link_to(post_page_title(post), post_path(post)).html_safe
  48. }.tap {|opts|
  49. opts[:comment_path] = post_path(post, anchor: mentioned.guid).html_safe if mentioned.instance_of?(Comment)
  50. }
  51. end
  52. def opts_for_birthday(note)
  53. {date: I18n.l(note.created_at, format: I18n.t("date.formats.fullmonth_day"))}
  54. end
  55. def notification_people_link(note, people=nil)
  56. actors =people || note.actors
  57. number_of_actors = actors.size
  58. sentence_translations = {:two_words_connector => " #{t('notifications.index.and')} ", :last_word_connector => ", #{t('notifications.index.and')} " }
  59. actor_links = actors.collect{ |person|
  60. person_link(person, :class => 'hovercardable')
  61. }
  62. if number_of_actors < 4
  63. message = actor_links.to_sentence(sentence_translations)
  64. else
  65. first, second, third, *others = actor_links
  66. others_sentence = others.to_sentence(sentence_translations)
  67. if others.count == 1
  68. others_sentence = " #{t('notifications.index.and')} " + others_sentence
  69. end
  70. message = "#{first}, #{second}, #{third},"
  71. message += "<a class='more' href='#'> #{t('notifications.index.and_others', :count =>(number_of_actors - 3))}</a>"
  72. message += "<span class='hidden'> #{others_sentence} </span>"
  73. end
  74. message.html_safe
  75. end
  76. def notification_message_for(note)
  77. object_link(note, notification_people_link(note))
  78. end
  79. def the_day(date)
  80. date.split('-')[2].to_i
  81. end
  82. def the_month(date)
  83. I18n.l(Date.strptime(date, '%Y-%m-%d'), :format => '%B')
  84. end
  85. def the_year(date)
  86. date.split('-')[0].to_i
  87. end
  88. def locale_date(date)
  89. I18n.l(Date.strptime(date, '%Y-%m-%d'), :format => I18n.t('date.formats.fullmonth_day'))
  90. end
  91. def display_year?(year, date)
  92. unless year
  93. Date.current.year != the_year(date)
  94. else
  95. year != the_year(date)
  96. end
  97. end
  98. end

app/helpers/notifier_helper.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. module NotifierHelper
  3. include PostsHelper
  4. # @param post [Post] The post object.
  5. # @param opts [Hash] Optional hash. Accepts :html parameter.
  6. # @return [String] The formatted post.
  7. def post_message(post, opts={})
  8. rendered = opts[:html] ? post.message&.markdownified_for_mail : post.message&.plain_text_without_markdown
  9. rendered.presence || post_page_title(post)
  10. end
  11. # @param comment [Comment] The comment to process.
  12. # @param opts [Hash] Optional hash. Accepts :html parameter.
  13. # @return [String] The formatted comment.
  14. def comment_message(comment, opts={})
  15. if comment.post.public?
  16. opts[:html] ? comment.message.markdownified_for_mail : comment.message.plain_text_without_markdown
  17. else
  18. I18n.translate "notifier.a_limited_post_comment"
  19. end
  20. end
  21. end

app/helpers/o_embed_helper.rb

0.0% lines covered

30 relevant lines. 0 lines covered and 30 lines missed.
    
  1. # frozen_string_literal: true
  2. module OEmbedHelper
  3. def o_embed_html(cache)
  4. data = cache.data
  5. data = {} if data.blank?
  6. title = data.fetch('title', cache.url)
  7. html = link_to(title, cache.url, :target => '_blank')
  8. return html unless data.has_key?('type')
  9. case data['type']
  10. when 'video', 'rich'
  11. if cache.is_trusted_and_has_html?
  12. html = data['html']
  13. elsif data.has_key?('thumbnail_url')
  14. html = link_to_oembed_image(cache)
  15. end
  16. when 'photo'
  17. if data.has_key?('url')
  18. img_options = cache.options_hash('')
  19. html = link_to_oembed_image(cache, '')
  20. end
  21. else
  22. end
  23. return html.gsub('http://', 'https://').html_safe
  24. end
  25. def link_to_oembed_image(cache, prefix = 'thumbnail_')
  26. link_to(oembed_image_tag(cache, prefix), cache.url, :target => '_blank')
  27. end
  28. def oembed_image_tag(cache, prefix)
  29. image_tag(cache.data[prefix + 'url'], cache.options_hash(prefix))
  30. end
  31. end

app/helpers/open_graph_helper.rb

0.0% lines covered

8 relevant lines. 0 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. module OpenGraphHelper
  3. def link_to_oembed_image(cache, prefix = 'thumbnail_')
  4. link_to(oembed_image_tag(cache, prefix), cache.url, :target => '_blank')
  5. end
  6. def oembed_image_tag(cache, prefix)
  7. image_tag(cache.data["#{prefix}url"], cache.options_hash(prefix))
  8. end
  9. end

app/helpers/people_helper.rb

0.0% lines covered

59 relevant lines. 0 lines covered and 59 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module PeopleHelper
  6. include ERB::Util
  7. def search_header
  8. if search_query.blank?
  9. content_tag(:h2, t('people.index.no_results'))
  10. else
  11. content_tag(:h2, id: 'search_title') do
  12. t('people.index.results_for', search_term: content_tag(:span, search_query, class: 'term')).html_safe + looking_for_tag_link
  13. end
  14. end
  15. end
  16. def birthday_format(bday)
  17. if bday.year <= 1004
  18. I18n.l bday, format: I18n.t("date.formats.birthday")
  19. else
  20. I18n.l bday, format: I18n.t("date.formats.birthday_with_year")
  21. end
  22. end
  23. def person_link(person, opts={})
  24. css_class = person_link_class(person, opts[:class])
  25. remote_or_hovercard_link = person_path(person)
  26. tag.a('data-hovercard': remote_or_hovercard_link, href: remote_or_hovercard_link, class: css_class) do
  27. opts[:display_name] || person.name
  28. end
  29. end
  30. def person_image_tag(person, size = :thumb_small)
  31. return "" if person.nil? || person.profile.nil?
  32. image_tag(person.profile.image_url(size: size), alt: person.name, class: "avatar img-responsive center-block",
  33. title: person.name, "data-person_id": person.id)
  34. end
  35. def person_image_link(person, opts={})
  36. return "" if person.nil? || person.profile.nil?
  37. if opts[:to] == :photos
  38. link_to person_image_tag(person, opts[:size]), person_photos_path(person)
  39. else
  40. tag.a(href: person_path(person), class: person_link_class(person, opts[:class])) do
  41. person_image_tag(person, opts[:size])
  42. end
  43. end
  44. end
  45. def local_or_remote_person_path(person, opts={})
  46. opts.merge!(:protocol => AppConfig.pod_uri.scheme, :host => AppConfig.pod_uri.authority)
  47. absolute = opts.delete(:absolute)
  48. if person.local?
  49. username = person.username
  50. unless username.include?('.')
  51. opts.merge!(:username => username)
  52. return absolute ? user_profile_url(opts) : user_profile_path(opts)
  53. end
  54. end
  55. absolute ? person_url(person, opts) : person_path(person, opts)
  56. end
  57. private
  58. def person_link_class(person, css_class)
  59. return css_class unless defined?(user_signed_in?) && user_signed_in?
  60. return "#{css_class} self" if current_user.person == person
  61. "#{css_class} hovercardable"
  62. end
  63. end

app/helpers/posts_helper.rb

0.0% lines covered

21 relevant lines. 0 lines covered and 21 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module PostsHelper
  6. def post_page_title(post, opts={})
  7. if post.is_a?(Photo)
  8. I18n.t "posts.show.photos_by", :count => 1, :author => post.status_message_author_name
  9. elsif post.is_a?(Reshare)
  10. I18n.t "posts.show.reshare_by", :author => post.author_name
  11. else
  12. if post.message.present?
  13. post.message.title opts
  14. elsif post.respond_to?(:photos) && post.photos.present?
  15. I18n.t "posts.show.photos_by", :count => post.photos.size, :author => post.author_name
  16. end
  17. end
  18. end
  19. def post_iframe_url(post_id, opts={})
  20. opts[:width] ||= 516
  21. opts[:height] ||= 315
  22. "<iframe src='#{AppConfig.url_to(Rails.application.routes.url_helpers.post_path(post_id))}' " \
  23. "width='#{opts[:width]}px' height='#{opts[:height]}px' frameBorder='0'></iframe>".html_safe
  24. end
  25. end

app/helpers/profile_helper.rb

0.0% lines covered

10 relevant lines. 0 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module ProfileHelper
  6. def upper_limit_date_of_birth
  7. minimum_year = AppConfig.settings.terms.minimum_age.get
  8. minimum_year = minimum_year ? minimum_year.to_i : 13
  9. minimum_year.years.ago.year
  10. end
  11. def lower_limit_date_of_birth
  12. 125.years.ago.year
  13. end
  14. end

app/helpers/publisher_helper.rb

0.0% lines covered

36 relevant lines. 0 lines covered and 36 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module PublisherHelper
  6. def available_services
  7. current_user.services.select {|service| AppConfig.configured_services.map(&:to_s).include? service.provider }
  8. end
  9. def service_button(service)
  10. provider_title = I18n.t("services.index.share_to", provider: service.provider.titleize)
  11. content_tag :div,
  12. class: "btn btn-link service_icon dim",
  13. title: "#{provider_title} (#{service.nickname})",
  14. id: service.provider,
  15. maxchar: service.class::MAX_CHARACTERS,
  16. data: {toggle: "tooltip", placement: "bottom"} do
  17. if service.provider == "wordpress"
  18. content_tag(:span, "", class: "social-media-logos-wordpress-16x16")
  19. else
  20. content_tag(:i, "", class: "entypo-social-#{service.provider} small")
  21. end
  22. end
  23. end
  24. def public_selected?(selected_aspects)
  25. "public" == selected_aspects.try(:first) || publisher_boolean?(:public)
  26. end
  27. def all_aspects_selected?(selected_aspects)
  28. !all_aspects.empty? && all_aspects.size == selected_aspects.size && !public_selected?(selected_aspects)
  29. end
  30. def aspect_selected?(aspect, selected_aspects)
  31. selected_aspects.include?(aspect) && !all_aspects_selected?(selected_aspects) && !public_selected?(selected_aspects)
  32. end
  33. def publisher_open?
  34. publisher_boolean?(:open)
  35. end
  36. private
  37. def publisher_boolean?(option)
  38. @stream.try(:publisher).try(option) == true
  39. end
  40. end

app/helpers/report_helper.rb

0.0% lines covered

18 relevant lines. 0 lines covered and 18 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module ReportHelper
  6. def report_content(report)
  7. case (item = report.item)
  8. when Post
  9. raw t("report.post_label", content: link_to(post_message(item), post_path(item.id)))
  10. when Comment
  11. raw t("report.comment_label", data: link_to(
  12. h(comment_message(item)),
  13. post_path(item.post.id, anchor: item.guid)
  14. ))
  15. else
  16. t("report.not_found")
  17. end
  18. end
  19. def unreviewed_reports_count
  20. @unreviewed_reports_count ||= Report.where(reviewed: false).size
  21. end
  22. end

app/helpers/services_helper.rb

0.0% lines covered

9 relevant lines. 0 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. module ServicesHelper
  3. def contact_proxy(friend)
  4. friend.contact || contact_proxy_template.dup.tap {|c| c.person = friend.person }
  5. end
  6. private
  7. def contact_proxy_template
  8. @@contact_proxy ||= Contact.new(:aspects => [])
  9. end
  10. end

app/helpers/sessions_helper.rb

0.0% lines covered

15 relevant lines. 0 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. module SessionsHelper
  3. def prefilled_username
  4. uri = Addressable::URI.parse(session["user_return_to"])
  5. uri&.query_values&.fetch("login_hint", "")
  6. end
  7. def display_registration_link?
  8. AppConfig.settings.enable_registrations? && controller_name != "registrations"
  9. end
  10. def display_password_reset_link?
  11. AppConfig.mail.enable? && devise_mapping.recoverable? && controller_name != "passwords"
  12. end
  13. def flash_class(name)
  14. {notice: "success", alert: "danger", error: "danger"}[name.to_sym]
  15. end
  16. end

app/helpers/statistics_helper.rb

0.0% lines covered

30 relevant lines. 0 lines covered and 30 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module StatisticsHelper
  6. def registrations_status statistics
  7. if statistics.open_registrations?
  8. I18n.t('statistics.open')
  9. else
  10. I18n.t('statistics.closed')
  11. end
  12. end
  13. def registrations_status_class statistics
  14. if statistics.open_registrations?
  15. "serv-enabled"
  16. else
  17. "serv-disabled"
  18. end
  19. end
  20. def service_status service, available_services
  21. if available_services.include? service.to_s
  22. I18n.t('statistics.enabled')
  23. else
  24. I18n.t('statistics.disabled')
  25. end
  26. end
  27. def service_class service, available_services
  28. if available_services.include? service.to_s
  29. "serv-enabled"
  30. else
  31. "serv-disabled"
  32. end
  33. end
  34. end

app/helpers/stream_helper.rb

0.0% lines covered

47 relevant lines. 0 lines covered and 47 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module StreamHelper
  6. def next_page_path(opts ={})
  7. if controller.instance_of?(TagsController)
  8. tag_path(:name => @stream.tag_name, :max_time => time_for_scroll(@stream))
  9. elsif controller.instance_of?(PeopleController)
  10. local_or_remote_person_path(@person, :max_time => time_for_scroll(@stream))
  11. elsif controller.instance_of?(StreamsController)
  12. next_stream_path
  13. else
  14. raise 'in order to use pagination for this new controller, update next_page_path in stream helper'
  15. end
  16. end
  17. def reshare?(post)
  18. post.instance_of?(Reshare)
  19. end
  20. private
  21. # rubocop:disable Rails/HelperInstanceVariable
  22. def next_stream_path
  23. if current_page?(:stream)
  24. stream_path(max_time: time_for_scroll(@stream))
  25. elsif current_page?(:activity_stream)
  26. activity_stream_path(max_time: time_for_scroll(@stream))
  27. elsif current_page?(:aspects_stream)
  28. aspects_stream_path(max_time: time_for_scroll(@stream), a_ids: session[:a_ids])
  29. elsif current_page?(:local_public_stream)
  30. local_public_stream_path(max_time: time_for_scroll(@stream))
  31. elsif current_page?(:public_stream)
  32. public_stream_path(max_time: time_for_scroll(@stream))
  33. elsif current_page?(:commented_stream)
  34. commented_stream_path(max_time: time_for_scroll(@stream))
  35. elsif current_page?(:liked_stream)
  36. liked_stream_path(max_time: time_for_scroll(@stream))
  37. elsif current_page?(:mentioned_stream)
  38. mentioned_stream_path(max_time: time_for_scroll(@stream))
  39. elsif current_page?(:followed_tags_stream)
  40. followed_tags_stream_path(max_time: time_for_scroll(@stream))
  41. else
  42. raise "in order to use pagination for this new stream, update next_stream_path in stream helper"
  43. end
  44. end
  45. # rubocop:enable Rails/HelperInstanceVariable
  46. def time_for_scroll(stream)
  47. if stream.stream_posts.empty?
  48. (Time.now() + 1).to_i
  49. else
  50. stream.stream_posts.last.send(stream.order.to_sym).to_i
  51. end
  52. end
  53. end

app/helpers/tags_helper.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. module TagsHelper
  3. def looking_for_tag_link
  4. return if search_query.include?('@') || normalize_tag_name(search_query).blank?
  5. content_tag('small') do
  6. t('people.index.looking_for', tag_link: tag_link(search_query)).html_safe
  7. end
  8. end
  9. def normalize_tag_name(tag)
  10. ActsAsTaggableOn::Tag.normalize(tag.to_s)
  11. end
  12. def tag_link(tag)
  13. link_to("##{tag}", tag_path(name: normalize_tag_name(tag)))
  14. end
  15. end

app/helpers/user_applications_helper.rb

0.0% lines covered

9 relevant lines. 0 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. module UserApplicationsHelper
  3. def user_application_name(app)
  4. if app.name?
  5. "#{html_escape app.name} (#{link_to(app.url, app.url)})"
  6. else
  7. link_to(app.url, app.url)
  8. end
  9. end
  10. end

app/helpers/users_helper.rb

0.0% lines covered

22 relevant lines. 0 lines covered and 22 lines missed.
    
  1. # frozen_string_literal: true
  2. module UsersHelper
  3. def owner_image_tag(size=nil)
  4. person_image_tag(current_user.person, size)
  5. end
  6. def owner_image_link
  7. person_image_link(current_user.person, :size => :thumb_small)
  8. end
  9. # Returns the path of the current color theme so that it
  10. # can be loaded in app/views/layouts/application.html.haml
  11. # and app/views/layouts/application.mobile.haml. If the user
  12. # is not signed in or has not specified a color theme, the
  13. # default (original) color theme is loaded.
  14. #
  15. # @example if user is not signed in
  16. # current_color_theme #=> "color_themes/original"
  17. # @example if user Alice has not selected a color theme
  18. # current_color_theme #=> "color_themes/original"
  19. # @example if user Alice has selected a "magenta" theme
  20. # current_color_theme #=> "color_themes/magenta"
  21. def current_color_theme
  22. if user_signed_in?
  23. color_theme = current_user.color_theme
  24. end
  25. color_theme ||= AppConfig.settings.default_color_theme
  26. "color_themes/#{color_theme}"
  27. end
  28. # Returns an array of the color themes available, as
  29. # specified from AVAILABLE_COLOR_THEMES in
  30. # config/initializers/color_themes.rb.
  31. #
  32. # @example if AVAILABLE_COLOR_THEMES = ["original", "dark_green"]
  33. # available_color_themes
  34. # #=> [["Original gray", "original"], ["Dark green", "dark_green"]]
  35. def available_color_themes
  36. opts = []
  37. AVAILABLE_COLOR_THEMES.map do |theme_code|
  38. opts << [I18n.t("color_themes.#{theme_code}"), theme_code]
  39. end
  40. opts
  41. end
  42. end

app/mailers/application_mailer.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ApplicationMailer < ActionMailer::Base
  3. 1 default from: "\"#{AppConfig.settings.pod_name}\" <#{AppConfig.mail.sender_address}>"
  4. end

app/mailers/diaspora_devise_mailer.rb

75.0% lines covered

4 relevant lines. 3 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class DiasporaDeviseMailer < Devise::Mailer
  3. 1 default from: "\"#{AppConfig.settings.pod_name}\" <#{AppConfig.mail.sender_address}>"
  4. 1 def self.mailer_name
  5. "devise/mailer"
  6. end
  7. end

app/mailers/export_mailer.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ExportMailer < ApplicationMailer
  3. 1 def export_complete_for(user)
  4. 4 send_mail(user, I18n.t("notifier.export_email.subject", name: user.name),
  5. I18n.t("notifier.export_email.body", url: download_profile_user_url, name: user.first_name))
  6. end
  7. 1 def export_failure_for(user)
  8. 8 send_mail(user, I18n.t("notifier.export_failure_email.subject", name: user.name),
  9. I18n.t("notifier.export_failure_email.body", name: user.first_name))
  10. end
  11. 1 def export_photos_complete_for(user)
  12. 4 send_mail(user, I18n.t("notifier.export_photos_email.subject", name: user.name),
  13. I18n.t("notifier.export_photos_email.body", url: download_photos_user_url, name: user.first_name))
  14. end
  15. 1 def export_photos_failure_for(user)
  16. 5 send_mail(user, I18n.t("notifier.export_photos_failure_email.subject", name: user.name),
  17. I18n.t("notifier.export_photos_failure_email.body", name: user.first_name))
  18. end
  19. 1 private
  20. 1 def send_mail(user, subject, body)
  21. 21 mail(to: user.email, subject: subject) do |format|
  22. 42 format.html { render "notifier/plain_markdown_email", locals: {body: body} }
  23. 42 format.text { render "notifier/plain_markdown_email", locals: {body: body} }
  24. end
  25. end
  26. end

app/mailers/maintenance.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Maintenance < ApplicationMailer
  3. 1 def account_removal_warning(user)
  4. 7 I18n.with_locale(user.language) do
  5. 7 body = I18n.t("notifier.remove_old_user.body",
  6. pod_url: AppConfig.environment.url,
  7. login_url: new_user_session_url,
  8. after_days: AppConfig.settings.maintenance.remove_old_users.after_days.to_s,
  9. remove_after: user.remove_after)
  10. 7 mail(to: user.email, subject: I18n.t("notifier.remove_old_user.subject")) do |format|
  11. 14 format.text { render "notifier/plain_markdown_email", locals: {body: body} }
  12. 14 format.html { render "notifier/plain_markdown_email", locals: {body: body} }
  13. end
  14. end
  15. end
  16. end

app/mailers/notification_mailers/also_commented.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class AlsoCommented < NotificationMailers::Base
  4. 1 attr_accessor :comment
  5. 1 delegate :post, to: :comment, prefix: true
  6. 1 def set_headers(comment_id)
  7. 16 @comment = Comment.find_by_id(comment_id)
  8. 16 if mail?
  9. 16 @headers[:in_reply_to] = @headers[:references] = "<#{@comment.parent.guid}@#{AppConfig.pod_uri.host}>"
  10. 16 if @comment.public?
  11. 7 @headers[:subject] = "Re: #{@comment.comment_email_subject}"
  12. else
  13. 9 @headers[:subject] = I18n.t("notifier.also_commented.limited_subject")
  14. end
  15. end
  16. end
  17. 1 def mail?
  18. 16 @recipient && @sender && @comment
  19. end
  20. end
  21. end

app/mailers/notification_mailers/base.rb

100.0% lines covered

34 relevant lines. 34 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class Base
  4. 1 include Diaspora::Logging
  5. 1 attr_accessor :recipient, :sender
  6. 1 delegate :unconfirmed_email, :confirm_email_token,
  7. :first_name, to: :recipient, prefix: true
  8. 1 delegate :first_name, :name, :sender, to: :sender, prefix: true
  9. 1 def initialize(recipient_id, sender_id=nil, *args)
  10. 734 @headers = {}
  11. 734 @recipient = User.find(recipient_id)
  12. 734 @sender = Person.find(sender_id) if sender_id.present?
  13. 734 log_mail(recipient_id, sender_id, self.class.to_s.underscore)
  14. 734 with_recipient_locale do
  15. 734 set_headers(*args)
  16. end
  17. end
  18. 1 def headers
  19. 734 default_headers.merge(@headers)
  20. end
  21. 1 def name_and_address(name, email)
  22. 1474 address = Mail::Address.new Addressable::IDNA.to_ascii(email)
  23. 1474 address.display_name = name
  24. 1474 address.format
  25. end
  26. 1 private
  27. 1 def default_headers
  28. 734 from_name = AppConfig.settings.pod_name
  29. 734 from_name += " (#{person_name(@sender)})" if @sender.present?
  30. {
  31. 734 from: name_and_address(from_name, AppConfig.mail.sender_address),
  32. to: name_and_address(person_name(@recipient), @recipient.email),
  33. template_name: self.class.name.demodulize.underscore
  34. }
  35. end
  36. 1 def person_name(person)
  37. 1454 if person.profile.full_name.empty?
  38. 1 person.username
  39. else
  40. 1453 person.name.gsub(/\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Presentation}/, "").strip
  41. end
  42. end
  43. 1 def with_recipient_locale(&block)
  44. 734 I18n.with_locale(@recipient.language, &block)
  45. end
  46. 1 def log_mail(recipient_id, sender_id, type)
  47. 734 log_string = "event=mail mail_type=#{type} recipient_id=#{recipient_id} sender_id=#{sender_id} " \
  48. " recipient_handle=#{@recipient.diaspora_handle}"
  49. 734 log_string = "#{log_string} sender_handle=#{@sender.diaspora_handle}" if sender_id.present?
  50. 734 logger.info log_string
  51. end
  52. end
  53. end

app/mailers/notification_mailers/comment_on_post.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class CommentOnPost < NotificationMailers::Base
  4. 1 attr_accessor :comment
  5. 1 def set_headers(comment_id)
  6. 24 @comment = Comment.find(comment_id)
  7. 24 @headers[:in_reply_to] = @headers[:references] = "<#{@comment.parent.guid}@#{AppConfig.pod_uri.host}>"
  8. 24 if @comment.public?
  9. 13 @headers[:subject] = "Re: #{@comment.comment_email_subject}"
  10. else
  11. 11 @headers[:subject] = I18n.t("notifier.comment_on_post.limited_subject")
  12. end
  13. end
  14. end
  15. end

app/mailers/notification_mailers/confirm_email.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class ConfirmEmail < NotificationMailers::Base
  4. 1 def set_headers
  5. 6 @headers[:to] = name_and_address(@recipient.first_name, @recipient.unconfirmed_email)
  6. 6 @headers[:subject] = I18n.t('notifier.confirm_email.subject', :unconfirmed_email => @recipient.unconfirmed_email)
  7. end
  8. end
  9. end

app/mailers/notification_mailers/contacts_birthday.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class ContactsBirthday < NotificationMailers::Base
  4. 1 attr_accessor :person
  5. 1 def set_headers(person_id)
  6. 3 @person = Person.find(person_id)
  7. 3 @headers[:subject] = I18n.t("notifier.contacts_birthday.subject", name: @person.name)
  8. end
  9. end
  10. end

app/mailers/notification_mailers/csrf_token_fail.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class CsrfTokenFail < NotificationMailers::Base
  4. 1 def set_headers
  5. 5 @headers[:subject] = I18n.t("notifier.csrf_token_fail.subject", name: @recipient.name)
  6. end
  7. end
  8. end

app/mailers/notification_mailers/liked.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class Liked < NotificationMailers::Base
  4. 1 attr_accessor :like
  5. 1 delegate :target, to: :like, prefix: true
  6. 1 def set_headers(like_id)
  7. 15 @like = Like.find(like_id)
  8. 15 @headers[:subject] = I18n.t('notifier.liked.liked', :name => @sender.name)
  9. 15 @headers[:in_reply_to] = @headers[:references] = "<#{@like.parent.guid}@#{AppConfig.pod_uri.host}>"
  10. end
  11. end
  12. end

app/mailers/notification_mailers/liked_comment.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class LikedComment < NotificationMailers::Base
  4. 1 attr_accessor :like
  5. 1 delegate :target, to: :like, prefix: true
  6. 1 def set_headers(like_id) # rubocop:disable Naming/AccessorMethodName
  7. 10 @like = Like.find(like_id)
  8. 10 @headers[:subject] = I18n.t("notifier.liked_comment.liked", name: @sender.name)
  9. 10 @headers[:in_reply_to] = @headers[:references] = "<#{@like.parent.commentable.guid}@#{AppConfig.pod_uri.host}>"
  10. end
  11. end
  12. end

app/mailers/notification_mailers/mentioned.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class Mentioned < NotificationMailers::Base
  4. 1 attr_accessor :post
  5. 1 delegate :author_name, to: :post, prefix: true
  6. 1 def set_headers(target_id)
  7. 108 @post = Mention.find_by_id(target_id).mentions_container
  8. 108 @headers[:subject] = I18n.t('notifier.mentioned.subject', :name => @sender.name)
  9. 108 @headers[:in_reply_to] = @headers[:references] = "<#{@post.guid}@#{AppConfig.pod_uri.host}>"
  10. end
  11. end
  12. end

app/mailers/notification_mailers/mentioned_in_comment.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class MentionedInComment < NotificationMailers::Base
  4. 1 attr_reader :comment
  5. 1 def set_headers(target_id) # rubocop:disable Naming/AccessorMethodName
  6. 20 @comment = Mention.find_by_id(target_id).mentions_container
  7. 20 @headers[:in_reply_to] = @headers[:references] = "<#{@comment.parent.guid}@#{AppConfig.pod_uri.host}>"
  8. 20 @headers[:subject] = I18n.t("notifier.mentioned.subject", name: @sender.name)
  9. end
  10. end
  11. end

app/mailers/notification_mailers/private_message.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class PrivateMessage < NotificationMailers::Base
  4. 1 attr_accessor :message, :conversation, :participants
  5. 1 def set_headers(message_id)
  6. 7 @message = Message.find_by_id(message_id)
  7. 7 @conversation = @message.conversation
  8. 7 @participants = @conversation.participants
  9. 7 @headers[:subject] = I18n.t("notifier.private_message.subject")
  10. 7 @headers[:in_reply_to] = @headers[:references] = "<#{@conversation.guid}@#{AppConfig.pod_uri.host}>"
  11. end
  12. end
  13. end

app/mailers/notification_mailers/reshared.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class Reshared < NotificationMailers::Base
  4. 1 attr_accessor :reshare
  5. 1 delegate :root, to: :reshare, prefix: true
  6. 1 def set_headers(reshare_id)
  7. 6 @reshare = Reshare.find(reshare_id)
  8. 6 @headers[:subject] = I18n.t('notifier.reshared.reshared', :name => @sender.name)
  9. 6 @headers[:in_reply_to] = @headers[:references] = "<#{@reshare.root_guid}@#{AppConfig.pod_uri.host}>"
  10. end
  11. end
  12. end

app/mailers/notification_mailers/started_sharing.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module NotificationMailers
  3. 1 class StartedSharing < NotificationMailers::Base
  4. 1 def set_headers(*_args) # rubocop:disable Naming/AccessorMethodName
  5. 514 @headers[:subject] = I18n.t("notifier.started_sharing.subject", name: @sender.name)
  6. end
  7. end
  8. end

app/mailers/notifier.rb

100.0% lines covered

35 relevant lines. 35 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Notifier < ApplicationMailer
  3. 1 helper :application
  4. 1 helper :notifier
  5. 1 helper :people
  6. 1 def self.admin(string, recipients, opts = {}, subject=nil)
  7. 3 mails = []
  8. 3 recipients.each do |rec|
  9. 11 mail = single_admin(string, rec, opts.dup, subject)
  10. 11 mails << mail
  11. end
  12. 3 mails
  13. end
  14. 1 def single_admin(string, recipient, opts={}, subject=nil)
  15. 14 @receiver = recipient
  16. 14 @string = string.html_safe
  17. 14 if attach = opts.delete(:attachments)
  18. 6 attach.each{ |f|
  19. 6 attachments[f[:name]] = f[:file]
  20. }
  21. end
  22. 14 subject ||= I18n.t("notifier.single_admin.subject")
  23. 14 default_opts = {to: @receiver.email, from: AppConfig.mail.sender_address, subject: subject}
  24. 14 default_opts.merge!(opts)
  25. 14 mail(default_opts)
  26. end
  27. 1 def invite(email, inviter, invitation_code, locale)
  28. 9 I18n.with_locale(locale) do
  29. 9 mail_opts = {to: email, from: "\"#{AppConfig.settings.pod_name}\" <#{AppConfig.mail.sender_address}>",
  30. subject: I18n.t("notifier.invited_you", name: inviter.name)}
  31. 9 name = inviter.full_name.empty? ? inviter.diaspora_handle : "#{inviter.name} (#{inviter.diaspora_handle})"
  32. 9 body = I18n.t("notifier.invite.message",
  33. invite_url: invite_code_url(invitation_code),
  34. diasporafoundation_url: "https://diasporafoundation.org/",
  35. user: name,
  36. diaspora_id: inviter.diaspora_handle)
  37. 9 mail(mail_opts) do |format|
  38. 18 format.text { render "notifier/plain_markdown_email", layout: nil, locals: {body: body} }
  39. 18 format.html { render "notifier/plain_markdown_email", layout: nil, locals: {body: body} }
  40. end
  41. end
  42. end
  43. 1 def send_notification(type, *args)
  44. 734 @notification = NotificationMailers.const_get(type.camelize).new(*args)
  45. 734 with_recipient_locale do
  46. 734 mail(@notification.headers)
  47. end
  48. end
  49. 1 private
  50. 1 def with_recipient_locale(&block)
  51. 734 I18n.with_locale(@notification.recipient.language, &block)
  52. end
  53. end

app/mailers/report_mailer.rb

100.0% lines covered

19 relevant lines. 19 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ReportMailer < ApplicationMailer
  3. 1 def self.new_report(report_id)
  4. 7 report = Report.find_by_id(report_id)
  5. 21 Role.moderators.map {|role| super(report.item_type, report.item_id, report.text, role) }
  6. end
  7. 1 def new_report(type, id, reason, role)
  8. resource = {
  9. 14 url: report_index_url,
  10. type: I18n.t("notifier.report_email.type.#{type.downcase}"),
  11. id: id,
  12. reason: reason
  13. }
  14. 14 person = Person.find(role.person_id)
  15. 14 return unless person.local?
  16. 14 user = User.find_by_id(person.owner_id)
  17. 14 return if user.user_preferences.exists?(email_type: :someone_reported)
  18. 14 I18n.with_locale(user.language) do
  19. 14 resource[:email] = user.email
  20. 14 format(resource)
  21. end
  22. end
  23. 1 private
  24. 1 def format(resource)
  25. 14 body = I18n.t("notifier.report_email.body", **resource)
  26. 14 mail(to: resource[:email], subject: I18n.t("notifier.report_email.subject", type: resource[:type])) do |format|
  27. 28 format.html { render "notifier/plain_markdown_email", locals: {body: body} }
  28. 28 format.text { render "notifier/plain_markdown_email", locals: {body: body} }
  29. end
  30. end
  31. end

app/models/account_deletion.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class AccountDeletion < ApplicationRecord
  6. 1 include Diaspora::Federated::Base
  7. 1 scope :uncompleted, -> { where("completed_at is null") }
  8. 1 belongs_to :person
  9. 1 after_commit :queue_delete_account, on: :create
  10. 1 delegate :diaspora_handle, to: :person
  11. 1 def queue_delete_account
  12. 17 Workers::DeleteAccount.perform_async(id)
  13. end
  14. 1 def perform!
  15. 5 Diaspora::Federation::Dispatcher.build(person.owner, self).dispatch if person.local?
  16. 5 AccountDeleter.new(person).perform!
  17. end
  18. 1 def subscribers
  19. 4 person.owner.contact_people.remote | Person.who_have_reshared_a_users_posts(person.owner).remote
  20. end
  21. 1 def public?
  22. 3 true
  23. end
  24. end

app/models/account_migration.rb

100.0% lines covered

133 relevant lines. 133 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class AccountMigration < ApplicationRecord
  3. 1 include Diaspora::Federated::Base
  4. 1 belongs_to :old_person, class_name: "Person"
  5. 1 belongs_to :new_person, class_name: "Person"
  6. 1 validates :old_person, uniqueness: true
  7. 1 validates :new_person, presence: true
  8. 1 after_create :lock_old_user!
  9. 1 attr_accessor :old_private_key
  10. 1 attr_writer :old_person_diaspora_id
  11. 1 attr_accessor :archive_contacts
  12. 1 def receive(*)
  13. 21 perform!
  14. end
  15. 1 def public?
  16. 41 true
  17. end
  18. 1 def sender
  19. 105 @sender ||= old_user || ephemeral_sender
  20. end
  21. 1 def perform!
  22. 105 raise "already performed" if performed?
  23. 104 validate_sender if locally_initiated?
  24. 103 tombstone_old_user_and_update_all_references if old_person
  25. 103 dispatch if locally_initiated?
  26. 103 dispatch_contacts
  27. 103 update(completed_at: Time.zone.now)
  28. end
  29. 1 def performed?
  30. 110 !completed_at.nil?
  31. end
  32. # Send migration to all imported contacts, but also send it to all contacts from the archive which weren't imported,
  33. # but maybe share with the old account, so they can update contact information and resend the contact message.
  34. # In case when a user migrated to our pod from a remote one, we include remote person to subscribers so that
  35. # the new pod is informed about the migration as well.
  36. 1 def subscribers
  37. 44 new_user.profile.subscribers.remote.to_a.tap do |subscribers|
  38. 44 subscribers.push(old_person) if old_person&.remote?
  39. 44 archive_contacts&.each do |contact|
  40. 8 diaspora_id = contact.fetch("account_id")
  41. 22 next if subscribers.any? {|s| s.diaspora_handle == diaspora_id }
  42. 3 person = Person.by_account_identifier(diaspora_id)
  43. 3 subscribers.push(person) if person&.remote?
  44. end
  45. end
  46. end
  47. # This method finds the newest user person profile in the migration chain.
  48. # If person migrated multiple times then #new_person may point to a closed account.
  49. # In this case in order to find open account we have to delegate new_person call to the next account_migration
  50. # instance in the chain.
  51. 1 def newest_person
  52. 1055 return new_person if new_person.account_migration.nil?
  53. 190 new_person.account_migration.newest_person
  54. end
  55. 1 private
  56. # Normally pod initiates migration locally when the new user is local. Then the pod creates AccountMigration object
  57. # itself. If new user is remote, then AccountMigration object is normally received via the federation and this is
  58. # remote initiation then.
  59. 1 def remotely_initiated?
  60. 309 new_person.remote?
  61. end
  62. 1 def locally_initiated?
  63. 207 !remotely_initiated?
  64. end
  65. 1 def old_user
  66. 647 old_person&.owner
  67. end
  68. 1 def new_user
  69. 203 new_person.owner
  70. end
  71. 1 def newest_user
  72. 145 newest_person.owner
  73. end
  74. 1 def lock_old_user!
  75. 123 old_user&.lock_access!
  76. end
  77. 1 def user_left_our_pod?
  78. 128 old_user && !new_user
  79. end
  80. 1 def user_changed_id_locally?
  81. 204 old_user && new_user
  82. end
  83. 1 def includes_photo_migration?
  84. 61 remote_photo_path.present?
  85. end
  86. 1 def tombstone_old_user_and_update_all_references
  87. 102 ActiveRecord::Base.transaction do
  88. 102 account_deleter.tombstone_person_and_profile
  89. 102 account_deleter.close_user if user_left_our_pod?
  90. 102 account_deleter.tombstone_user if user_changed_id_locally?
  91. 102 update_all_references
  92. end
  93. end
  94. # We need to resend contacts of users of our pod for the remote new person so that the remote pod received this
  95. # contact information from the authoritative source.
  96. 1 def dispatch_contacts
  97. 103 newest_person.contacts.sharing.each do |contact|
  98. 12 Diaspora::Federation::Dispatcher.defer_dispatch(contact.user, contact)
  99. end
  100. end
  101. 1 def dispatch
  102. 42 Diaspora::Federation::Dispatcher.build(sender, self).dispatch
  103. end
  104. 1 EphemeralUser = Struct.new(:diaspora_handle, :serialized_private_key) do
  105. 1 def id
  106. 13 diaspora_handle
  107. end
  108. 1 def encryption_key
  109. 13 OpenSSL::PKey::RSA.new(serialized_private_key)
  110. end
  111. end
  112. 1 def old_person_diaspora_id
  113. 28 old_person&.diaspora_handle || @old_person_diaspora_id
  114. end
  115. 1 def ephemeral_sender
  116. 16 if old_private_key.nil? || old_person_diaspora_id.nil?
  117. 2 raise "can't build sender without old private key and diaspora ID defined"
  118. end
  119. 14 EphemeralUser.new(old_person_diaspora_id, old_private_key)
  120. end
  121. 1 def validate_sender
  122. 43 sender # sender method raises exception when sender can't be instantiated
  123. end
  124. 1 def update_all_references
  125. 102 update_remote_photo_path if remotely_initiated? && includes_photo_migration?
  126. 102 update_person_references
  127. 102 update_user_references if user_changed_id_locally?
  128. end
  129. 1 def person_references
  130. 102 references = Person.reflections.reject {|key, _|
  131. 2040 %w[profile owner notifications pod account_deletion account_migration].include?(key)
  132. }
  133. 102 references.map {|key, value|
  134. 1428 {value.foreign_key => key}
  135. }
  136. end
  137. 1 def user_references
  138. 29 references = User.reflections.reject {|key, _|
  139. 667 %w[
  140. person profile auto_follow_back_aspect invited_by aspect_memberships contact_people followed_tags
  141. ignored_people conversation_visibilities pairwise_pseudonymous_identifiers conversations o_auth_applications
  142. ].include?(key)
  143. }
  144. 29 references.map {|key, value|
  145. 319 {value.foreign_key => key}
  146. }
  147. end
  148. 1 def eliminate_person_duplicates
  149. 102 duplicate_person_contacts.destroy_all
  150. 102 duplicate_person_likes.destroy_all
  151. 102 duplicate_person_participations.destroy_all
  152. 102 duplicate_person_poll_participations.destroy_all
  153. end
  154. 1 def duplicate_person_contacts
  155. 102 Contact
  156. .joins("INNER JOIN contacts as c2 ON (contacts.user_id = c2.user_id AND contacts.person_id=#{old_person.id} AND"\
  157. " c2.person_id=#{newest_person.id})")
  158. end
  159. 1 def duplicate_person_likes
  160. 102 Like
  161. .joins("INNER JOIN likes as l2 ON (likes.target_id = l2.target_id "\
  162. "AND likes.target_type = l2.target_type "\
  163. "AND likes.author_id=#{old_person.id} AND"\
  164. " l2.author_id=#{newest_person.id})")
  165. end
  166. 1 def duplicate_person_participations
  167. 102 Participation
  168. .joins("INNER JOIN participations as p2 ON (participations.target_id = p2.target_id "\
  169. "AND participations.target_type = p2.target_type "\
  170. "AND participations.author_id=#{old_person.id} AND"\
  171. " p2.author_id=#{newest_person.id})")
  172. end
  173. 1 def duplicate_person_poll_participations
  174. 102 PollParticipation
  175. .joins("INNER JOIN poll_participations as p2 ON (poll_participations.poll_id = p2.poll_id "\
  176. "AND poll_participations.author_id=#{old_person.id} AND"\
  177. " p2.author_id=#{newest_person.id})")
  178. end
  179. 1 def eliminate_user_duplicates
  180. 29 Aspect
  181. .joins("INNER JOIN aspects as a2 ON (aspects.name = a2.name AND aspects.user_id=#{old_user.id}
  182. AND a2.user_id=#{newest_user.id})")
  183. .destroy_all
  184. 29 Contact
  185. .joins("INNER JOIN contacts as c2 ON (contacts.person_id = c2.person_id AND contacts.user_id=#{old_user.id} AND"\
  186. " c2.user_id=#{newest_user.id})")
  187. .destroy_all
  188. 29 TagFollowing
  189. .joins("INNER JOIN tag_followings as t2 ON (tag_followings.tag_id = t2.tag_id AND"\
  190. " tag_followings.user_id=#{old_user.id} AND t2.user_id=#{newest_user.id})")
  191. .destroy_all
  192. end
  193. 1 def update_remote_photo_path
  194. 26 Photo.where(author: old_person)
  195. .update_all(remote_photo_path: remote_photo_path) # rubocop:disable Rails/SkipsModelValidations
  196. 26 return unless user_left_our_pod?
  197. 12 Photo.where(author: old_person).find_in_batches do |batch|
  198. 6 batch.each do |photo|
  199. 6 photo.processed_image = nil
  200. 6 photo.unprocessed_image = nil
  201. 6 logger.warn "Error cleaning up photo #{photo.id}" unless photo.save
  202. end
  203. end
  204. end
  205. 1 def update_person_references
  206. 102 logger.debug "Updating references from person id=#{old_person.id} to person id=#{newest_person.id}"
  207. 102 eliminate_person_duplicates
  208. 102 update_references(person_references, old_person, newest_person.id)
  209. end
  210. 1 def update_user_references
  211. 29 logger.debug "Updating references from user id=#{old_user.id} to user id=#{newest_user.id}"
  212. 29 eliminate_user_duplicates
  213. 29 update_references(user_references, old_user, newest_user.id)
  214. end
  215. 1 def update_references(references, object, new_id)
  216. 131 references.each do |pair|
  217. 1747 key_id = pair.flatten[0]
  218. 1747 association = pair.flatten[1]
  219. 1747 object.send(association).update_all(key_id => new_id)
  220. end
  221. end
  222. 1 def account_deleter
  223. 155 @account_deleter ||= AccountDeleter.new(old_person)
  224. end
  225. end

app/models/api/openid_connect/authorization.rb

0.0% lines covered

92 relevant lines. 0 lines covered and 92 lines missed.
    
  1. # frozen_string_literal: true
  2. # Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/models/authorization.rb
  3. module Api
  4. module OpenidConnect
  5. class Authorization < ApplicationRecord
  6. belongs_to :user
  7. belongs_to :o_auth_application
  8. validates :user, uniqueness: {scope: :o_auth_application}
  9. validate :validate_scope_names
  10. serialize :scopes, JSON
  11. has_many :o_auth_access_tokens, dependent: :destroy
  12. before_validation :setup, on: :create
  13. scope :with_redirect_uri, ->(given_uri) { where redirect_uri: given_uri }
  14. SCOPES = %w[
  15. contacts:modify
  16. contacts:read
  17. conversations
  18. email
  19. interactions
  20. name
  21. nickname
  22. notifications
  23. openid
  24. picture
  25. private:modify
  26. private:read
  27. profile
  28. profile:modify
  29. profile:read_private
  30. public:modify
  31. public:read
  32. sub
  33. tags:modify
  34. tags:read
  35. ].freeze
  36. def setup
  37. self.refresh_token = SecureRandom.hex(32)
  38. end
  39. def validate_scope_names
  40. return unless scopes
  41. scopes.each do |scope|
  42. errors.add(:scope, "is not a valid scope name") unless SCOPES.include? scope
  43. end
  44. end
  45. # Inspired by https://github.com/nov/openid_connect_sample/blob/master/app/models/access_token.rb#L26
  46. def accessible?(required_scopes=nil)
  47. Array(required_scopes).all? { |required_scope|
  48. scopes.include? required_scope
  49. }
  50. end
  51. def create_code
  52. SecureRandom.hex(32).tap do |code|
  53. update!(code: code)
  54. update!(code_used: false)
  55. end
  56. end
  57. def create_access_token
  58. o_auth_access_tokens.create!.bearer_token
  59. end
  60. def create_id_token
  61. IdToken.new(self, nonce)
  62. end
  63. def self.find_by_client_id_user_and_scopes(client_id, user, scopes)
  64. app = Api::OpenidConnect::OAuthApplication.where(client_id: client_id)
  65. authorizations = where(o_auth_application: app, user: user).all
  66. authorizations.each do |authorization|
  67. if authorization.scopes.uniq.sort == Array(scopes).uniq.sort
  68. return authorization
  69. end
  70. end
  71. nil
  72. end
  73. def self.find_by_client_id_and_user(client_id, user)
  74. app = Api::OpenidConnect::OAuthApplication.where(client_id: client_id)
  75. find_by(o_auth_application: app, user: user)
  76. end
  77. def self.find_by_refresh_token(client_id, refresh_token)
  78. app = Api::OpenidConnect::OAuthApplication.where(client_id: client_id)
  79. find_by(o_auth_application: app, refresh_token: refresh_token)
  80. end
  81. def self.use_code(code)
  82. return unless code
  83. auth = find_by(code: code)
  84. return unless auth
  85. if auth.code_used
  86. auth.destroy
  87. nil
  88. else
  89. auth.update!(code_used: true)
  90. auth
  91. end
  92. end
  93. end
  94. end
  95. end

app/models/api/openid_connect/o_auth_access_token.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2011 nov matake
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. # See https://github.com/nov/openid_connect_sample/blob/master/app/models/access_token.rb
  23. 1 module Api
  24. 1 module OpenidConnect
  25. 1 class OAuthAccessToken < ApplicationRecord
  26. 1 belongs_to :authorization
  27. 1 before_validation :setup, on: :create
  28. 1 validates :token, presence: true, uniqueness: true
  29. 477 scope :valid, ->(time) { where("expires_at >= ?", time) }
  30. 1 def setup
  31. 1358 self.token = SecureRandom.hex(32)
  32. 1358 self.expires_at = 24.hours.from_now
  33. end
  34. 1 def bearer_token
  35. 1358 @bearer_token ||= Rack::OAuth2::AccessToken::Bearer.new(
  36. access_token: token,
  37. 1358 expires_in: (expires_at - Time.zone.now.utc).to_i
  38. )
  39. end
  40. end
  41. end
  42. end

app/models/api/openid_connect/o_auth_application.rb

0.0% lines covered

88 relevant lines. 0 lines covered and 88 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2011 nov matake
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. # See https://github.com/nov/openid_connect_sample/blob/master/app/models/client.rb
  23. require "digest"
  24. module Api
  25. module OpenidConnect
  26. class OAuthApplication < ApplicationRecord
  27. has_many :authorizations, dependent: :destroy
  28. has_many :user, through: :authorizations
  29. validates :client_id, presence: true, uniqueness: true
  30. validates :client_secret, presence: true
  31. validates :client_name, uniqueness: {scope: :redirect_uris}
  32. %i(redirect_uris response_types grant_types contacts jwks).each do |serializable|
  33. serialize serializable, JSON
  34. end
  35. before_validation :setup, on: :create
  36. before_validation do
  37. redirect_uris.sort!
  38. end
  39. def as_json(opts={})
  40. data = super
  41. data["client_secret_expires_at"] = 0
  42. data["token_endpoint_auth_method"] ||= "client_secret_post"
  43. data
  44. end
  45. def setup
  46. self.client_id = SecureRandom.hex(16)
  47. self.client_secret = SecureRandom.hex(32)
  48. end
  49. def image_uri
  50. logo_uri ? Diaspora::Camo.image_url(logo_uri) : nil
  51. end
  52. class << self
  53. def available_response_types
  54. ["id_token", "id_token token", "code"]
  55. end
  56. def register!(registrar)
  57. registrar.validate!
  58. build_client_application(registrar)
  59. end
  60. private
  61. def build_client_application(registrar)
  62. attributes = registrar_attributes(registrar)
  63. check_sector_identifier_uri(attributes)
  64. check_redirect_uris(attributes)
  65. create! attributes
  66. end
  67. def check_sector_identifier_uri(attributes)
  68. sector_identifier_uri = attributes[:sector_identifier_uri]
  69. return unless sector_identifier_uri
  70. response = Faraday.get(sector_identifier_uri)
  71. sector_identifier_uri_json = JSON.parse(response.body)
  72. redirect_uris = attributes[:redirect_uris]
  73. sector_identifier_uri_includes_redirect_uris = (redirect_uris - sector_identifier_uri_json).empty?
  74. return if sector_identifier_uri_includes_redirect_uris
  75. raise Api::OpenidConnect::Error::InvalidSectorIdentifierUri.new
  76. end
  77. def check_redirect_uris(attributes)
  78. redirect_uris = attributes[:redirect_uris]
  79. uri_array = redirect_uris.map {|uri| URI(uri) }
  80. any_uri_contains_fragment = uri_array.any? {|uri| !uri.fragment.nil? }
  81. return unless any_uri_contains_fragment
  82. raise Api::OpenidConnect::Error::InvalidRedirectUri.new
  83. end
  84. def supported_metadata
  85. %i(client_name response_types grant_types application_type
  86. contacts logo_uri client_uri policy_uri tos_uri redirect_uris
  87. sector_identifier_uri subject_type token_endpoint_auth_method jwks jwks_uri)
  88. end
  89. def registrar_attributes(registrar)
  90. supported_metadata.each_with_object({}) do |key, attr|
  91. value = registrar.public_send(key)
  92. next unless value
  93. case key
  94. when :subject_type
  95. attr[:ppid] = (value == "pairwise")
  96. when :jwks_uri
  97. response = Faraday.get(value)
  98. attr[:jwks] = response.body
  99. attr[:jwks_uri] = value
  100. when :jwks
  101. attr[:jwks] = value.to_json
  102. else
  103. attr[key] = value
  104. end
  105. end
  106. end
  107. end
  108. end
  109. end
  110. end

app/models/api/openid_connect/pairwise_pseudonymous_identifier.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2011 nov matake
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. # See https://github.com/nov/openid_connect_sample/blob/master/app/models/pairwise_pseudonymous_identifier.rb
  23. 1 module Api
  24. 1 module OpenidConnect
  25. 1 class PairwisePseudonymousIdentifier < ApplicationRecord
  26. 1 self.table_name = "ppid"
  27. 1 belongs_to :o_auth_application, optional: true
  28. 1 belongs_to :user
  29. 1 validates :identifier, presence: true, uniqueness: {scope: :user}
  30. 1 validates :guid, presence: true, uniqueness: true
  31. 1 before_validation :setup, on: :create
  32. 1 private
  33. 1 def setup
  34. 9 self.guid = SecureRandom.hex(16)
  35. end
  36. end
  37. end
  38. end

app/models/application_record.rb

0.0% lines covered

3 relevant lines. 0 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. class ApplicationRecord < ActiveRecord::Base
  3. self.abstract_class = true
  4. end

app/models/aspect.rb

95.0% lines covered

20 relevant lines. 19 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Aspect < ApplicationRecord
  6. 1 belongs_to :user
  7. 1 has_many :aspect_memberships, :dependent => :destroy
  8. 1 has_many :contacts, :through => :aspect_memberships
  9. 1 has_many :aspect_visibilities, :dependent => :destroy
  10. 1 has_many :posts, :through => :aspect_visibilities, :source => :shareable, :source_type => 'Post'
  11. 1 has_many :photos, :through => :aspect_visibilities, :source => :shareable, :source_type => 'Photo'
  12. 1 validates :name, :presence => true, :length => { :maximum => 20 }
  13. 1 validates_uniqueness_of :name, :scope => :user_id, :case_sensitive => false
  14. 1 before_validation do
  15. 2935 name.strip!
  16. end
  17. 1 before_create do
  18. 996 self.order_id ||= Aspect.where(user_id: user_id).maximum(:order_id || 0).to_i + 1
  19. end
  20. 1 def to_s
  21. 53 name
  22. end
  23. 1 def << (shareable)
  24. 87 case shareable
  25. when Post
  26. 65 self.posts << shareable
  27. when Photo
  28. 22 self.photos << shareable
  29. else
  30. raise "Unknown shareable type '#{shareable.class.base_class.to_s}'"
  31. end
  32. end
  33. end

app/models/aspect_membership.rb

81.82% lines covered

11 relevant lines. 9 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class AspectMembership < ApplicationRecord
  6. 1 belongs_to :aspect
  7. 1 belongs_to :contact
  8. 1 has_one :user, :through => :contact
  9. 1 has_one :person, :through => :contact
  10. 1 before_destroy do
  11. 71 user&.disconnect(contact) if contact&.aspects&.size == 1
  12. 71 true
  13. end
  14. 1 def as_json(opts={})
  15. {
  16. :id => self.id,
  17. :person_id => self.person.id,
  18. :contact_id => self.contact.id,
  19. :aspect_id => self.aspect_id,
  20. :aspect_ids => self.contact.aspects.map{|a| a.id}
  21. }
  22. end
  23. end

app/models/aspect_visibility.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class AspectVisibility < ApplicationRecord
  6. 1 belongs_to :aspect
  7. 1 belongs_to :shareable, :polymorphic => true
  8. 1 validates :aspect, uniqueness: {scope: %i(shareable_id shareable_type)}
  9. end

app/models/block.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Block < ApplicationRecord
  3. 1 belongs_to :person
  4. 1 belongs_to :user
  5. 1 delegate :name, :diaspora_handle, to: :person, prefix: true
  6. 1 validates :person_id, uniqueness: {scope: :user_id}
  7. 1 validate :not_blocking_yourself
  8. 1 def not_blocking_yourself
  9. 78 return unless user.person.id == person_id
  10. 1 errors.add(:person_id, "stop blocking yourself!")
  11. end
  12. # @return [Array<Person>] The recipient of the block
  13. 1 def subscribers
  14. 8 [person]
  15. end
  16. end

app/models/comment.rb

100.0% lines covered

46 relevant lines. 46 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Comment < ApplicationRecord
  6. 1 include Diaspora::Federated::Base
  7. 1 include Diaspora::Fields::Guid
  8. 1 include Diaspora::Fields::Author
  9. 1 include Diaspora::Relayable
  10. 1 include Diaspora::Taggable
  11. 1 include Diaspora::Likeable
  12. 1 include Diaspora::MentionsContainer
  13. 1 include Reference::Source
  14. 1 acts_as_taggable_on :tags
  15. 1 extract_tags_from :text
  16. 1 before_create :build_tags
  17. 1 belongs_to :commentable, :touch => true, :polymorphic => true
  18. 1 alias_attribute :post, :commentable
  19. 1 alias_attribute :parent, :commentable
  20. 1 delegate :name, to: :author, prefix: true
  21. 1 delegate :comment_email_subject, to: :parent
  22. 1 delegate :author_name, to: :parent, prefix: true
  23. 1 validates :text, :presence => true, :length => {:maximum => 65535}
  24. 1 has_many :reports, as: :item
  25. 1 has_one :signature, class_name: "CommentSignature", dependent: :delete
  26. 33 scope :including_author, -> { includes(:author => :profile) }
  27. 33 scope :for_a_stream, -> { including_author.merge(order('created_at ASC')) }
  28. 1 scope :all_public, -> {
  29. 3 where("commentable_type = 'Post' AND EXISTS(
  30. SELECT 1 FROM posts WHERE posts.id = commentable_id AND posts.public = true
  31. )")
  32. }
  33. 1 before_save do
  34. 674 self.text.strip! unless self.text.nil?
  35. end
  36. 1 after_commit on: :create do
  37. 674 parent.update_comments_counter
  38. 674 parent.touch(:interacted_at) if parent.respond_to?(:interacted_at)
  39. end
  40. 1 after_destroy do
  41. 23 self.parent.update_comments_counter
  42. 23 participation = author.participations.find_by(target_id: post.id)
  43. 23 participation.unparticipate! if participation.present?
  44. end
  45. 1 def text= text
  46. 741 self[:text] = text.to_s.strip #to_s if for nil, for whatever reason
  47. end
  48. 1 def add_mention_subscribers?
  49. 55 super && parent.author.local?
  50. end
  51. 1 class Generator < Diaspora::Federated::Generator
  52. 1 def self.federated_class
  53. 455 Comment
  54. end
  55. 1 def initialize(person, target, text)
  56. 455 @text = text
  57. 455 super(person, target)
  58. end
  59. 1 def relayable_options
  60. 455 {post: @target, text: @text}
  61. end
  62. end
  63. end

app/models/comment_signature.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class CommentSignature < ApplicationRecord
  3. 1 include Diaspora::Signature
  4. 1 self.primary_key = :comment_id
  5. 1 belongs_to :comment
  6. end

app/models/contact.rb

95.56% lines covered

45 relevant lines. 43 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Contact < ApplicationRecord
  6. 1 include Diaspora::Federated::Base
  7. 1 belongs_to :user
  8. 1 belongs_to :person
  9. 1 validates :person_id, uniqueness: {scope: :user_id}
  10. 1 delegate :name, :diaspora_handle, :guid, :first_name,
  11. to: :person, prefix: true
  12. 1 has_many :aspect_memberships, dependent: :destroy
  13. 1 has_many :aspects, through: :aspect_memberships
  14. 1 validate :not_contact_for_self,
  15. :not_blocked_user,
  16. :not_contact_with_closed_account
  17. 1 before_destroy :destroy_notifications
  18. 25 scope :all_contacts_of_person, ->(x) { where(person_id: x.id) }
  19. # contact.sharing is true when contact.person is sharing with contact.user
  20. 220 scope :sharing, -> { where(sharing: true) }
  21. # contact.receiving is true when contact.user is sharing with contact.person
  22. 336 scope :receiving, -> { where(receiving: true) }
  23. 100 scope :mutual, -> { sharing.receiving }
  24. 2 scope :for_a_stream, -> { includes(:aspects, person: :profile).order("profiles.last_name ASC") }
  25. 14 scope :only_sharing, -> { sharing.where(receiving: false) }
  26. 1 def destroy_notifications
  27. 96 Notification.where(
  28. target_type: "Person",
  29. target_id: person_id,
  30. recipient_id: user_id,
  31. type: "Notifications::StartedSharing"
  32. ).destroy_all
  33. end
  34. 1 def mutual?
  35. 69 sharing && receiving
  36. end
  37. 1 def in_aspect?(aspect)
  38. 2 if aspect_memberships.loaded?
  39. aspect_memberships.detect{ |am| am.aspect_id == aspect.id }
  40. 2 elsif aspects.loaded?
  41. aspects.detect{ |a| a.id == aspect.id }
  42. else
  43. 2 AspectMembership.exists?(:contact_id => self.id, :aspect_id => aspect.id)
  44. end
  45. end
  46. # Follows back if user setting is set so
  47. 1 def receive(_recipient_user_ids)
  48. 514 user.share_with(person, user.auto_follow_back_aspect) if user.auto_follow_back && !receiving
  49. end
  50. # object for local recipients
  51. 1 def object_to_receive
  52. 511 Contact.create_or_update_sharing_contact(person.owner, user.person)
  53. end
  54. # @return [Array<Person>] The recipient of the contact
  55. 1 def subscribers
  56. 591 [person]
  57. end
  58. # creates or updates a contact with active sharing flag. Returns nil if already sharing.
  59. 1 def self.create_or_update_sharing_contact(recipient, sender)
  60. 515 contact = recipient.contacts.find_or_initialize_by(person_id: sender.id)
  61. 515 return if contact.sharing
  62. 514 contact.update(sharing: true)
  63. 514 contact
  64. end
  65. 1 private
  66. 1 def not_contact_with_closed_account
  67. 2236 errors.add(:base, "Cannot be in contact with a closed account") if person_id && person.closed_account?
  68. end
  69. 1 def not_contact_for_self
  70. 2236 errors.add(:base, "Cannot create self-contact") if person_id && person.owner == user
  71. end
  72. 1 def not_blocked_user
  73. 2236 if receiving && user && user.blocks.where(person_id: person_id).exists?
  74. 2 errors.add(:base, "Cannot connect to an ignored user")
  75. end
  76. end
  77. end

app/models/conversation.rb

100.0% lines covered

42 relevant lines. 42 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Conversation < ApplicationRecord
  3. 1 include Diaspora::Federated::Base
  4. 1 include Diaspora::Fields::Guid
  5. 1 include Diaspora::Fields::Author
  6. 1 has_many :conversation_visibilities, dependent: :destroy
  7. 1 has_many :participants, class_name: "Person", through: :conversation_visibilities, source: :person
  8. 509 has_many :messages, -> { order("created_at ASC") }, inverse_of: :conversation
  9. 1 validate :local_recipients
  10. 1 def local_recipients
  11. 267 recipients.each do |recipient|
  12. 240 if recipient.local?
  13. 209 unless recipient.owner.contacts.where(person_id: author.id).any? ||
  14. 7 (author.owner && author.owner.podmin_account?)
  15. 4 errors.add(:all_recipients, "recipient not allowed")
  16. end
  17. end
  18. end
  19. end
  20. 1 accepts_nested_attributes_for :messages
  21. 1 def recipients
  22. 270 self.participants - [self.author]
  23. end
  24. 1 def first_unread_message(user)
  25. 8 if visibility = self.conversation_visibilities.where(:person_id => user.person.id).where('unread > 0').first
  26. 2 self.messages.to_a[-visibility.unread]
  27. end
  28. end
  29. 1 def set_read(user)
  30. 7 update_read_for(user, read: true)
  31. end
  32. 1 def update_read_for(user, read:)
  33. 9 visibility = conversation_visibilities.find_by(person_id: user.person.id)
  34. 9 return unless visibility
  35. 9 visibility.unread = read ? 0 : 1
  36. 9 visibility.save
  37. end
  38. 1 def participant_handles
  39. 3 participants.map(&:diaspora_handle).join(";")
  40. end
  41. 1 def participant_handles=(handles)
  42. 6 handles.split(";").each do |handle|
  43. 12 participants << Person.find_or_fetch_by_identifier(handle)
  44. end
  45. end
  46. 1 def last_author
  47. 33 return unless @last_author.present? || messages.size > 0
  48. 33 @last_author_id ||= messages.pluck(:author_id).last
  49. 33 @last_author ||= Person.includes(:profile).find_by(id: @last_author_id)
  50. end
  51. 1 def ordered_participants
  52. 18 @ordered_participants ||= (messages.map(&:author).reverse + participants).uniq
  53. end
  54. 1 def subject
  55. 111 self[:subject].blank? ? I18n.t("conversations.new.subject_default") : self[:subject]
  56. end
  57. 1 def subscribers
  58. 3 recipients
  59. end
  60. end

app/models/conversation_visibility.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ConversationVisibility < ApplicationRecord
  3. 1 belongs_to :conversation
  4. 1 belongs_to :person
  5. 1 after_destroy :check_orphan_conversation
  6. 1 private
  7. 1 def check_orphan_conversation
  8. 23 conversation = Conversation.find_by_id(self.conversation.id)
  9. 23 if conversation
  10. 23 conversation.destroy if conversation.participants.count == 0
  11. end
  12. end
  13. end

app/models/invitation_code.rb

95.65% lines covered

23 relevant lines. 22 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class InvitationCode < ApplicationRecord
  3. 1 belongs_to :user
  4. 1 before_create :generate_token, :set_default_invite_count
  5. 1 delegate :name, to: :user, prefix: true
  6. 1 def to_param
  7. 50 token
  8. end
  9. 1 def can_be_used?
  10. 7 count > 0 && AppConfig.settings.invitations.open?
  11. end
  12. 1 def add_invites!
  13. update(count: count + 100)
  14. end
  15. 1 def use!
  16. 2 update(count: count - 1)
  17. end
  18. 1 def generate_token
  19. 63 loop do
  20. 63 self.token = SecureRandom.hex(6)
  21. 63 break unless InvitationCode.default_scoped.exists?(token: token)
  22. end
  23. end
  24. 1 def self.default_inviter_or(user)
  25. 6 if AppConfig.admins.account.present?
  26. 1 inviter = User.find_by_username(AppConfig.admins.account.get)
  27. end
  28. 6 inviter ||= user
  29. 6 inviter
  30. end
  31. 1 def set_default_invite_count
  32. 63 self.count = AppConfig['settings.invitations.count'] || 25
  33. end
  34. end

app/models/like.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Like < ApplicationRecord
  6. 1 include Diaspora::Federated::Base
  7. 1 include Diaspora::Fields::Guid
  8. 1 include Diaspora::Fields::Author
  9. 1 include Diaspora::Fields::Target
  10. 1 include Diaspora::Relayable
  11. 1 has_one :signature, class_name: "LikeSignature", dependent: :delete
  12. 1 alias_attribute :parent, :target
  13. 1 class Generator < Diaspora::Federated::Generator
  14. 1 def self.federated_class
  15. 300 Like
  16. end
  17. 1 def relayable_options
  18. 300 {:target => @target, :positive => true}
  19. end
  20. end
  21. 1 after_commit :on => :create do
  22. 410 self.parent.update_likes_counter
  23. end
  24. 1 after_destroy do
  25. 21 self.parent.update_likes_counter
  26. 21 participation_target_id = parent.is_a?(Comment) ? parent.commentable.id : parent.id
  27. 21 participation = author.participations.find_by(target_id: participation_target_id)
  28. 21 participation.unparticipate! if participation.present?
  29. end
  30. # NOTE API V1 to be extracted
  31. 1 acts_as_api
  32. 1 api_accessible :backbone do |t|
  33. 1 t.add :id
  34. 1 t.add :guid
  35. 1 t.add :author
  36. 1 t.add :created_at
  37. end
  38. end

app/models/like_signature.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class LikeSignature < ApplicationRecord
  3. 1 include Diaspora::Signature
  4. 1 self.primary_key = :like_id
  5. 1 belongs_to :like
  6. end

app/models/location.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Location < ApplicationRecord
  3. 1 before_validation :split_coords, on: :create
  4. 1 validates_presence_of :lat, :lng
  5. 1 attr_accessor :coordinates
  6. 1 include Diaspora::Federated::Base
  7. 1 belongs_to :status_message
  8. 1 def split_coords
  9. 20 self.lat, self.lng = coordinates.split(',') if coordinates.present?
  10. end
  11. end

app/models/mention.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Mention < ApplicationRecord
  6. belongs_to :mentions_container, polymorphic: true
  7. belongs_to :person
  8. scope :local, -> {
  9. joins(:person).where.not(people: {owner_id: nil})
  10. }
  11. after_destroy :delete_notification
  12. def delete_notification
  13. Notification.where(target_type: self.class.name, target_id: id).destroy_all
  14. end
  15. end

app/models/message.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Message < ApplicationRecord
  3. 1 include Diaspora::Federated::Base
  4. 1 include Diaspora::Fields::Guid
  5. 1 include Diaspora::Fields::Author
  6. 1 include Reference::Source
  7. 1 belongs_to :conversation, touch: true
  8. 1 delegate :name, to: :author, prefix: true
  9. 1 validates :text, presence: true
  10. 1 validate :participant_of_parent_conversation
  11. 1 def conversation_guid=(guid)
  12. 16 self.conversation_id = Conversation.where(guid: guid).ids.first
  13. end
  14. 1 def increase_unread(user)
  15. 13 vis = ConversationVisibility.find_by(conversation_id: conversation_id, person_id: user.person.id)
  16. 13 return unless vis
  17. 13 vis.unread += 1
  18. 13 vis.save
  19. end
  20. 1 def message
  21. 24 @message ||= Diaspora::MessageRenderer.new text
  22. end
  23. # @return [Array<Person>]
  24. 1 def subscribers
  25. 2 conversation.participants
  26. end
  27. 1 private
  28. 1 def participant_of_parent_conversation
  29. 261 return unless conversation&.participants&.exclude?(author)
  30. 3 errors.add(:base, "Author is not participating in the conversation")
  31. end
  32. end

app/models/notification.rb

0.0% lines covered

40 relevant lines. 0 lines covered and 40 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. class Notification < ApplicationRecord
  7. include Diaspora::Fields::Guid
  8. belongs_to :recipient, class_name: "User"
  9. has_many :notification_actors, dependent: :delete_all
  10. has_many :actors, class_name: "Person", through: :notification_actors, source: :person
  11. belongs_to :target, polymorphic: true
  12. def self.for(recipient, opts={})
  13. where(opts.merge!(recipient_id: recipient.id)).order("updated_at DESC")
  14. end
  15. def email_the_user(target, actor)
  16. recipient.mail(mail_job, recipient_id, actor.id, target.id)
  17. end
  18. def set_read_state( read_state )
  19. update_column(:unread, !read_state)
  20. end
  21. def mail_job
  22. raise NotImplementedError.new("Subclass this.")
  23. end
  24. def linked_object
  25. target
  26. end
  27. def self.concatenate_or_create(recipient, target, actor)
  28. return nil if suppress_notification?(recipient, actor)
  29. find_or_initialize_by(recipient: recipient, target: target, unread: true).tap do |notification|
  30. notification.actors |= [actor]
  31. # Explicitly touch the notification to update updated_at whenever new actor is inserted in notification.
  32. if notification.new_record? || notification.changed?
  33. notification.save!
  34. else
  35. notification.touch
  36. end
  37. end
  38. end
  39. def self.create_notification(recipient, target, actor)
  40. return nil if suppress_notification?(recipient, actor)
  41. create(recipient: recipient, target: target, actors: [actor])
  42. end
  43. private_class_method def self.suppress_notification?(recipient, actor)
  44. recipient.blocks.where(person: actor).exists?
  45. end
  46. end

app/models/notification_actor.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class NotificationActor < ApplicationRecord
  6. 1 belongs_to :notification
  7. 1 belongs_to :person
  8. end

app/models/notifications/also_commented.rb

0.0% lines covered

20 relevant lines. 0 lines covered and 20 lines missed.
    
  1. # frozen_string_literal: true
  2. module Notifications
  3. class AlsoCommented < Notification
  4. include Notifications::Commented
  5. def mail_job
  6. Workers::Mail::AlsoCommented
  7. end
  8. def popup_translation_key
  9. "notifications.also_commented"
  10. end
  11. def self.notify(comment, _recipient_user_ids)
  12. actor = comment.author
  13. commentable = comment.commentable
  14. recipient_ids = commentable.participants.local.where.not(id: [commentable.author_id, actor.id]).pluck(:owner_id)
  15. User.where(id: recipient_ids).find_each do |recipient|
  16. next if recipient.is_shareable_hidden?(commentable) || mention_notification_exists?(comment, recipient.person)
  17. concatenate_or_create(recipient, commentable, actor).try(:email_the_user, comment, actor)
  18. end
  19. end
  20. end
  21. end

app/models/notifications/comment_on_post.rb

92.31% lines covered

13 relevant lines. 12 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class CommentOnPost < Notification
  4. 1 include Notifications::Commented
  5. 1 def mail_job
  6. 10 Workers::Mail::CommentOnPost
  7. end
  8. 1 def popup_translation_key
  9. "notifications.comment_on_post"
  10. end
  11. 1 def self.notify(comment, _recipient_user_ids)
  12. 23 actor = comment.author
  13. 23 commentable_author = comment.commentable.author
  14. 23 return unless commentable_author.local? && actor != commentable_author
  15. 16 return if mention_notification_exists?(comment, commentable_author)
  16. 10 concatenate_or_create(commentable_author.owner, comment.commentable, actor).email_the_user(comment, actor)
  17. end
  18. end
  19. end

app/models/notifications/commented.rb

0.0% lines covered

13 relevant lines. 0 lines covered and 13 lines missed.
    
  1. # frozen_string_literal: true
  2. module Notifications
  3. module Commented
  4. extend ActiveSupport::Concern
  5. def deleted_translation_key
  6. "notifications.also_commented_deleted"
  7. end
  8. module ClassMethods
  9. def mention_notification_exists?(comment, recipient_person)
  10. Notifications::MentionedInComment.exists?(target: comment.mentions.where(person: recipient_person))
  11. end
  12. end
  13. end
  14. end

app/models/notifications/contacts_birthday.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class ContactsBirthday < Notification
  4. 1 def mail_job
  5. 2 Workers::Mail::ContactsBirthday
  6. end
  7. 1 def popup_translation_key
  8. 2 "notifications.contacts_birthday"
  9. end
  10. 1 def self.notify(contact, _recipient_user_ids)
  11. 3 recipient = contact.user
  12. 3 actor = contact.person
  13. 3 create_notification(recipient, actor, actor).try(:email_the_user, actor, actor)
  14. end
  15. end
  16. end

app/models/notifications/liked.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class Liked < Notification
  4. 1 def mail_job
  5. 35 Workers::Mail::Liked
  6. end
  7. 1 def popup_translation_key
  8. 5 "notifications.liked"
  9. end
  10. 1 def deleted_translation_key
  11. 2 "notifications.liked_post_deleted"
  12. end
  13. 1 def self.notify(like, _recipient_user_ids)
  14. 35 actor = like.author
  15. 35 target_author = like.target.author
  16. 35 return unless like.target_type == "Post" && target_author.local? && actor != target_author
  17. 35 concatenate_or_create(target_author.owner, like.target, actor).email_the_user(like, actor)
  18. end
  19. end
  20. end

app/models/notifications/liked_comment.rb

69.23% lines covered

13 relevant lines. 9 lines covered and 4 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class LikedComment < Notification
  4. 1 def mail_job
  5. Workers::Mail::LikedComment
  6. end
  7. 1 def popup_translation_key
  8. "notifications.liked_comment"
  9. end
  10. 1 def deleted_translation_key
  11. "notifications.liked_comment_deleted"
  12. end
  13. 1 def self.notify(like, _recipient_user_ids)
  14. 3 actor = like.author
  15. 3 target_author = like.target.author
  16. 3 return unless like.target_type == "Comment" && target_author.local? && actor != target_author
  17. concatenate_or_create(target_author.owner, like.target, actor).email_the_user(like, actor)
  18. end
  19. end
  20. end

app/models/notifications/mentioned.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 module Mentioned
  4. 1 extend ActiveSupport::Concern
  5. 1 def linked_object
  6. 87 target.mentions_container
  7. end
  8. 1 module ClassMethods
  9. 1 def notify(mentionable, recipient_user_ids)
  10. 1015 actor = mentionable.author
  11. 1015 relevant_mentions = filter_mentions(
  12. mentionable.mentions.local.where.not(person: actor),
  13. mentionable,
  14. recipient_user_ids
  15. )
  16. 1015 relevant_mentions.each do |mention|
  17. 138 recipient = mention.person.owner
  18. 138 unless exists?(recipient: recipient, target: mention)
  19. 137 create_notification(recipient, mention, actor).try(:email_the_user, mention, actor)
  20. end
  21. end
  22. end
  23. end
  24. end
  25. end

app/models/notifications/mentioned_in_comment.rb

94.74% lines covered

19 relevant lines. 18 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class MentionedInComment < Notification
  4. 1 include Notifications::Mentioned
  5. 1 def popup_translation_key
  6. 1 "notifications.mentioned_in_comment"
  7. end
  8. 1 def deleted_translation_key
  9. "notifications.mentioned_in_comment_deleted"
  10. end
  11. 1 def self.filter_mentions(mentions, mentionable, _recipient_user_ids)
  12. 25 mentions.includes(:person).merge(Person.allowed_to_be_mentioned_in_a_comment_to(mentionable.parent))
  13. end
  14. 1 def mail_job
  15. 20 if !recipient.user_preferences.exists?(email_type: "mentioned_in_comment")
  16. 18 Workers::Mail::MentionedInComment
  17. 2 elsif shareable.author.owner_id == recipient_id
  18. 1 Workers::Mail::CommentOnPost
  19. 1 elsif shareable.participants.local.where(owner_id: recipient_id)
  20. 1 Workers::Mail::AlsoCommented
  21. end
  22. end
  23. 1 private
  24. 1 def shareable
  25. 3 linked_object.parent
  26. end
  27. end
  28. end

app/models/notifications/mentioned_in_post.rb

91.67% lines covered

12 relevant lines. 11 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class MentionedInPost < Notification
  4. 1 include Notifications::Mentioned
  5. 1 def mail_job
  6. 113 Workers::Mail::Mentioned
  7. end
  8. 1 def popup_translation_key
  9. 1 "notifications.mentioned"
  10. end
  11. 1 def deleted_translation_key
  12. "notifications.mentioned_deleted"
  13. end
  14. 1 def self.filter_mentions(mentions, mentionable, recipient_user_ids)
  15. 985 return mentions if mentionable.public
  16. 583 mentions.where(person: Person.where(owner_id: recipient_user_ids).ids)
  17. end
  18. end
  19. end

app/models/notifications/private_message.rb

93.33% lines covered

15 relevant lines. 14 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class PrivateMessage < Notification
  4. 1 def mail_job
  5. 5 Workers::Mail::PrivateMessage
  6. end
  7. 1 def popup_translation_key
  8. "notifications.private_message"
  9. end
  10. 1 def self.notify(object, _recipient_user_ids)
  11. 6 case object
  12. when Conversation
  13. 3 object.messages.each {|message| notify_message(message) }
  14. when Message
  15. 4 notify_message(object)
  16. end
  17. end
  18. 1 private_class_method def self.notify_message(message)
  19. 5 recipient_ids = message.conversation.participants.local.where.not(id: message.author_id).pluck(:owner_id)
  20. 5 User.where(id: recipient_ids).find_each do |recipient|
  21. 5 message.increase_unread(recipient)
  22. 5 new(recipient: recipient).email_the_user(message, message.author)
  23. end
  24. end
  25. end
  26. end

app/models/notifications/reshared.rb

83.33% lines covered

12 relevant lines. 10 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class Reshared < Notification
  4. 1 def mail_job
  5. 4 Workers::Mail::Reshared
  6. end
  7. 1 def popup_translation_key
  8. "notifications.reshared"
  9. end
  10. 1 def deleted_translation_key
  11. "notifications.reshared_post_deleted"
  12. end
  13. 1 def self.notify(reshare, _recipient_user_ids)
  14. 20 return unless reshare.root.present? && reshare.root.author.local?
  15. 5 actor = reshare.author
  16. 5 concatenate_or_create(reshare.root.author.owner, reshare.root, actor).try(:email_the_user, reshare, actor)
  17. end
  18. end
  19. end

app/models/notifications/started_sharing.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Notifications
  3. 1 class StartedSharing < Notification
  4. 1 def mail_job
  5. 514 Workers::Mail::StartedSharing
  6. end
  7. 1 def popup_translation_key
  8. 7 "notifications.started_sharing"
  9. end
  10. 1 def self.notify(contact, _recipient_user_ids)
  11. 515 sender = contact.person
  12. 515 create_notification(contact.user, sender, sender).try(:email_the_user, sender, sender)
  13. end
  14. 1 def contact
  15. 3 recipient.contact_for(target)
  16. end
  17. end
  18. end

app/models/o_embed_cache.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class OEmbedCache < ApplicationRecord
  3. 1 serialize :data
  4. 1 validates :data, :presence => true
  5. 1 has_many :posts
  6. # NOTE API V1 to be extracted
  7. 1 acts_as_api
  8. 1 api_accessible :backbone do |t|
  9. 1 t.add :data
  10. end
  11. 1 def self.find_or_create_by(opts)
  12. 7 cache = OEmbedCache.find_or_initialize_by(opts)
  13. 7 return cache if cache.persisted?
  14. 5 cache.fetch_and_save_oembed_data! # make after create callback and drop this method ?
  15. 5 cache
  16. end
  17. 1 def fetch_and_save_oembed_data!
  18. begin
  19. 5 response = OEmbed::Providers.get(self.url, {:maxwidth => 420, :maxheight => 420, :frame => 1, :iframe => 1})
  20. rescue => e
  21. # noop
  22. else
  23. 4 self.data = response.fields
  24. 4 self.data['trusted_endpoint_url'] = response.provider.endpoint
  25. 4 self.save
  26. end
  27. end
  28. 1 def is_trusted_and_has_html?
  29. 4 self.from_trusted? and self.data.has_key?('html')
  30. end
  31. 1 def from_trusted?
  32. 4 SECURE_ENDPOINTS.include?(self.data['trusted_endpoint_url'])
  33. end
  34. 1 def options_hash(prefix = 'thumbnail_')
  35. 2 return nil unless self.data.has_key?(prefix + 'url')
  36. {
  37. 2 :height => self.data.fetch(prefix + 'height', ''),
  38. :width => self.data.fetch(prefix + 'width', ''),
  39. :alt => self.data.fetch('title', ''),
  40. }
  41. end
  42. end

app/models/open_graph_cache.rb

97.3% lines covered

37 relevant lines. 36 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class OpenGraphCache < ApplicationRecord
  3. 1 validates :title, :presence => true
  4. 1 validates :ob_type, :presence => true
  5. 1 validates :image, :presence => true
  6. 1 validates :url, :presence => true
  7. 1 has_many :posts
  8. 1 acts_as_api
  9. 1 api_accessible :backbone do |t|
  10. 1 t.add :title
  11. 1 t.add :ob_type
  12. 1 t.add :image
  13. 1 t.add :description
  14. 1 t.add :url
  15. 1 t.add :video_url
  16. end
  17. 1 def image
  18. 20 if AppConfig.privacy.camo.proxy_opengraph_thumbnails?
  19. Diaspora::Camo.image_url(self[:image])
  20. else
  21. 20 self[:image]
  22. end
  23. end
  24. 1 def self.find_or_create_by(opts)
  25. 7 cache = OpenGraphCache.find_or_initialize_by(opts)
  26. 7 cache.fetch_and_save_opengraph_data! unless cache.persisted?
  27. 7 cache if cache.persisted? # Make this an after create callback and drop this method ?
  28. end
  29. 1 def fetch_and_save_opengraph_data!
  30. 8 uri = URI.parse(url.start_with?("http") ? url : "http://#{url}")
  31. 8 uri.normalize!
  32. 8 object = OpenGraphReader.fetch!(uri)
  33. 6 return unless object
  34. 6 self.title = object.og.title.truncate(255)
  35. 6 self.ob_type = object.og.type
  36. 6 self.image = object.og.image.url
  37. 6 self.url = object.og.url
  38. 6 self.description = object.og.description
  39. 6 if object.og.video.try(:secure_url) && secure_video_url?(object.og.video.secure_url)
  40. 1 self.video_url = object.og.video.secure_url
  41. end
  42. 6 self.save
  43. rescue OpenGraphReader::NoOpenGraphDataError, OpenGraphReader::InvalidObjectError
  44. end
  45. 1 def secure_video_url?(url)
  46. 4 SECURE_OPENGRAPH_VIDEO_URLS.any? {|u| u =~ url }
  47. end
  48. end

app/models/participation.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Participation < ApplicationRecord
  3. 1 include Diaspora::Federated::Base
  4. 1 include Diaspora::Fields::Guid
  5. 1 include Diaspora::Fields::Author
  6. 1 include Diaspora::Fields::Target
  7. 1 class Generator < Diaspora::Federated::Generator
  8. 1 def self.federated_class
  9. 317 Participation
  10. end
  11. 1 def relayable_options
  12. 317 {:target => @target}
  13. end
  14. end
  15. 1 def unparticipate!
  16. 20 if count == 1
  17. 13 destroy
  18. else
  19. 7 update!(count: count.pred)
  20. end
  21. end
  22. # @return [Array<Person>]
  23. 1 def subscribers
  24. 12 [target.author]
  25. end
  26. # NOTE API V1 to be extracted
  27. 1 acts_as_api
  28. 1 api_accessible :backbone do |t|
  29. 1 t.add :id
  30. 1 t.add :guid
  31. 1 t.add :author
  32. 1 t.add :created_at
  33. end
  34. end

app/models/person.rb

96.28% lines covered

188 relevant lines. 181 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Person < ApplicationRecord
  6. 1 include Diaspora::Fields::Guid
  7. # NOTE API V1 to be extracted
  8. 1 acts_as_api
  9. 1 api_accessible :backbone do |t|
  10. 1 t.add :id
  11. 1 t.add :guid
  12. 1 t.add :name
  13. 1 t.add lambda { |person|
  14. 361 person.diaspora_handle
  15. }, :as => :diaspora_id
  16. 1 t.add lambda { |person|
  17. 361 {small: person.profile.image_url(size: :thumb_small),
  18. medium: person.profile.image_url(size: :thumb_medium),
  19. large: person.profile.image_url(size: :thumb_large)}
  20. }, as: :avatar
  21. end
  22. 1 has_one :profile, dependent: :destroy
  23. 1 delegate :last_name, :full_name, :image_url, :tag_string, :bio, :location,
  24. :gender, :birthday, :formatted_birthday, :tags, :searchable,
  25. :public_details?, to: :profile
  26. 1 accepts_nested_attributes_for :profile
  27. 1 before_validation :downcase_diaspora_handle
  28. 1 def downcase_diaspora_handle
  29. 11295 diaspora_handle.downcase! unless diaspora_handle.blank?
  30. end
  31. 1 has_many :contacts, :dependent => :destroy # Other people's contacts for this person
  32. 1 has_many :posts, :foreign_key => :author_id, :dependent => :destroy # This person's own posts
  33. 1 has_many :photos, :foreign_key => :author_id, :dependent => :destroy # This person's own photos
  34. 1 has_many :comments, :foreign_key => :author_id, :dependent => :destroy # This person's own comments
  35. 1 has_many :likes, foreign_key: :author_id, dependent: :destroy # This person's own likes
  36. 1 has_many :participations, :foreign_key => :author_id, :dependent => :destroy
  37. 1 has_many :poll_participations, foreign_key: :author_id, dependent: :destroy
  38. 1 has_many :conversation_visibilities, dependent: :destroy
  39. 1 has_many :messages, foreign_key: :author_id, dependent: :destroy
  40. 1 has_many :conversations, foreign_key: :author_id, dependent: :destroy
  41. 1 has_many :blocks, dependent: :destroy
  42. 1 has_many :roles
  43. 1 belongs_to :owner, class_name: "User", optional: true
  44. 1 belongs_to :pod, optional: true
  45. 1 has_many :notification_actors
  46. 1 has_many :notifications, :through => :notification_actors
  47. 1 has_many :mentions, :dependent => :destroy
  48. 1 has_one :account_deletion, dependent: :destroy
  49. 1 has_one :account_migration, foreign_key: :old_person_id, dependent: :nullify, inverse_of: :old_person
  50. 1 validate :owner_xor_pod
  51. 1 validate :other_person_with_same_guid, on: :create
  52. 1 validates :profile, :presence => true
  53. 1 validates :serialized_public_key, :presence => true
  54. 1 validates :diaspora_handle, :uniqueness => true
  55. 1 scope :searchable, -> (user) {
  56. 48 if user
  57. 45 joins("LEFT OUTER JOIN contacts ON contacts.user_id = #{user.id} AND contacts.person_id = people.id")
  58. .joins(:profile)
  59. .where("profiles.searchable = true OR contacts.user_id = ?", user.id)
  60. else
  61. 3 joins(:profile).where(profiles: {searchable: true})
  62. end
  63. }
  64. 55 scope :remote, -> { where('people.owner_id IS NULL') }
  65. 39 scope :local, -> { where('people.owner_id IS NOT NULL') }
  66. 17 scope :for_json, -> { select("people.id, people.guid, people.diaspora_handle").includes(:profile) }
  67. # @note user is passed in here defensively
  68. 1 scope :all_from_aspects, ->(aspect_ids, user) {
  69. 8 joins(:contacts => :aspect_memberships).
  70. where(:contacts => {:user_id => user.id}).
  71. where(:aspect_memberships => {:aspect_id => aspect_ids})
  72. }
  73. 1 scope :unique_from_aspects, ->(aspect_ids, user) {
  74. 1 all_from_aspects(aspect_ids, user).select('DISTINCT people.*')
  75. }
  76. #not defensive
  77. 1 scope :in_aspects, ->(aspect_ids) {
  78. 1148 joins(contacts: :aspect_memberships)
  79. .where(aspect_memberships: {aspect_id: aspect_ids}).distinct
  80. }
  81. 1 scope :in_all_aspects, ->(aspect_ids) {
  82. joins(contacts: :aspect_memberships)
  83. .where(aspect_memberships: {aspect_id: aspect_ids})
  84. }
  85. 1 scope :contacts_of, ->(user) {
  86. 17 joins(:contacts).where(contacts: {user_id: user.id})
  87. }
  88. 1 scope :profile_tagged_with, ->(tag_name) {
  89. 16 joins(:profile => :tags)
  90. .where(:tags => {:name => tag_name})
  91. .where('profiles.searchable IS TRUE')
  92. }
  93. 1 scope :who_have_reshared_a_users_posts, ->(user) {
  94. 5 joins(:posts)
  95. .where(:posts => {:root_guid => StatusMessage.guids_for_author(user.person), :type => 'Reshare'} )
  96. }
  97. # This scope selects people where the full name contains the search_str or diaspora ID
  98. # starts with the search_str.
  99. # However, if the search_str doesn't have more than 1 non-whitespace character, it'll return an empty set.
  100. # @param [String] search substring
  101. # @return [Person::ActiveRecord_Relation]
  102. 1 scope :find_by_substring, ->(search_str) {
  103. 87 search_str = search_str.strip
  104. 87 if search_str.blank? || search_str.size < 2
  105. 11 none
  106. else
  107. 76 sql, tokens = search_query_string(search_str)
  108. 76 joins(:profile).where(sql, *tokens)
  109. end
  110. }
  111. # Left joins likes and comments to a specific post where people are authors of these comments and likes
  112. # @param [String, Integer] post ID for which comments and likes should be joined
  113. # @return [Person::ActiveRecord_Relation]
  114. 1 scope :left_join_visible_post_interactions_on_authorship, ->(post_id) {
  115. 38 comments_sql = <<-SQL
  116. LEFT OUTER JOIN comments ON
  117. comments.author_id = people.id AND comments.commentable_type = 'Post' AND comments.commentable_id = #{post_id}
  118. SQL
  119. 38 likes_sql = <<-SQL
  120. LEFT OUTER JOIN likes ON
  121. likes.author_id = people.id AND likes.target_type = 'Post' AND likes.target_id = #{post_id}
  122. SQL
  123. 38 joins(comments_sql).joins(likes_sql)
  124. }
  125. # Selects people who can be mentioned in a comment to a specific post. For public posts all people
  126. # are allowed, so no additional constraints are added. For private posts selection is limited to
  127. # people who have posted comments or likes for this post.
  128. # @param [Post] the post for which we query mentionable in comments people
  129. # @return [Person::ActiveRecord_Relation]
  130. 1 scope :allowed_to_be_mentioned_in_a_comment_to, ->(post) {
  131. 42 allowed = if post.public?
  132. 21 all
  133. else
  134. 21 left_join_visible_post_interactions_on_authorship(post.id)
  135. .where("comments.id IS NOT NULL OR likes.id IS NOT NULL OR people.id = #{post.author_id}")
  136. end
  137. 42 allowed.distinct
  138. }
  139. # This scope adds sorting of people in the order, appropriate for suggesting to a user (current user) who
  140. # has requested a list of the people mentionable in a comment for a specific post.
  141. # Sorts people in the following priority: post author > commenters > likers > contacts > non-contacts
  142. # @param [Post] post for which the mentionable in comment people list is requested
  143. # @param [User] user who requests the people list
  144. # @return [Person::ActiveRecord_Relation]
  145. 1 scope :sort_for_mention_suggestion, ->(post, user) {
  146. 17 left_join_visible_post_interactions_on_authorship(post.id)
  147. .joins("LEFT OUTER JOIN contacts ON people.id = contacts.person_id AND contacts.user_id = #{user.id}")
  148. .joins(:profile)
  149. 17 .select(<<-SQL
  150. 17 people.id = #{unscoped { post.author_id }} AS is_author,
  151. comments.id IS NOT NULL AS is_commenter,
  152. likes.id IS NOT NULL AS is_liker,
  153. contacts.id IS NOT NULL AS is_contact
  154. SQL
  155. )
  156. 17 .order(Arel.sql(<<-SQL
  157. is_author DESC,
  158. is_commenter DESC,
  159. is_liker DESC,
  160. is_contact DESC,
  161. profiles.full_name,
  162. people.diaspora_handle
  163. SQL
  164. ))
  165. }
  166. 1 def self.community_spotlight
  167. 11 Person.joins(:roles).where(:roles => {:name => 'spotlight'})
  168. end
  169. # Set a default of an empty profile when a new Person record is instantiated.
  170. # Passing :profile => nil to Person.new will instantiate a person with no profile.
  171. # Calling Person.new with a block:
  172. # Person.new do |p|
  173. # p.profile = nil
  174. # end
  175. # will not work! The nil profile will be overriden with an empty one.
  176. 1 def initialize(params={})
  177. 5703 params = {} if params.nil?
  178. 5703 profile_set = params.has_key?(:profile) || params.has_key?("profile")
  179. 5703 params[:profile_attributes] = params.delete(:profile) if params.has_key?(:profile) && params[:profile].is_a?(Hash)
  180. 5703 super
  181. 5703 self.profile ||= Profile.new unless profile_set
  182. end
  183. 1 def self.find_from_guid_or_username(params)
  184. 97 p = if params[:id].present?
  185. 89 Person.find_by(guid: params[:id])
  186. 8 elsif params[:username].present? && u = User.find_by_username(params[:username])
  187. 6 u.person
  188. else
  189. 2 nil
  190. end
  191. 97 raise ActiveRecord::RecordNotFound unless p.present?
  192. 91 p
  193. end
  194. 1 def to_param
  195. 605 self.guid
  196. end
  197. 1 def self.search_query_string(query)
  198. 76 query = query.downcase
  199. 76 like_operator = AppConfig.postgres? ? "ILIKE" : "LIKE"
  200. 76 where_clause = <<-SQL
  201. profiles.full_name #{like_operator} ? OR
  202. people.diaspora_handle #{like_operator} ?
  203. SQL
  204. 76 q_tokens = []
  205. 248 q_tokens[0] = query.to_s.strip.gsub(/(\s|$|^)/) { "%#{$1}" }
  206. 76 q_tokens[1] = q_tokens[0].gsub(/\s/,'').gsub('%','')
  207. 76 q_tokens[1] << "%"
  208. 76 [where_clause, q_tokens]
  209. end
  210. 1 def self.search(search_str, user, only_contacts: false, mutual: false)
  211. 49 query = find_by_substring(search_str)
  212. 49 return query if query.is_a?(ActiveRecord::NullRelation)
  213. 42 query = if only_contacts
  214. 12 query.contacts_of(user)
  215. else
  216. 30 query.searchable(user)
  217. end
  218. 42 query = query.where(contacts: {sharing: true, receiving: true}) if mutual
  219. 42 query.where(closed_account: false)
  220. .order([Arel.sql("contacts.user_id IS NULL"), "profiles.last_name ASC", "profiles.first_name ASC"])
  221. end
  222. 1 def name(opts = {})
  223. 6556 if self.profile.nil?
  224. fix_profile
  225. end
  226. 6556 @name ||= Person.name_from_attrs(self.profile.first_name, self.profile.last_name, self.diaspora_handle)
  227. end
  228. 1 def self.name_from_attrs(first_name, last_name, diaspora_handle)
  229. 3361 first_name.blank? && last_name.blank? ? diaspora_handle : "#{first_name.to_s.strip} #{last_name.to_s.strip}".strip
  230. end
  231. 1 def first_name
  232. 1438 @first_name ||= if profile.nil? || profile.first_name.nil? || profile.first_name.blank?
  233. 2 self.diaspora_handle.split('@').first
  234. else
  235. 754 names = profile.first_name.to_s.split(/\s/)
  236. 754 str = names[0...-1].join(' ')
  237. 754 str = names[0] if str.blank?
  238. 754 str
  239. end
  240. end
  241. 1 def username
  242. 1579 @username ||= owner ? owner.username : diaspora_handle.split("@")[0]
  243. end
  244. 1 def author
  245. 2 self
  246. end
  247. 1 def owns?(obj)
  248. 36 self.id == obj.author_id
  249. end
  250. 1 def url
  251. 23 url_to "/"
  252. end
  253. 1 def profile_url
  254. 12 url_to "/u/#{username}"
  255. end
  256. 1 def atom_url
  257. 498 url_to "/public/#{username}.atom"
  258. end
  259. 1 def receive_url
  260. 151 url_to "/receive/users/#{guid}"
  261. end
  262. # @param path [String]
  263. # @return [String]
  264. 1 def url_to(path)
  265. 763 local? ? AppConfig.url_to(path) : pod.url_to(path)
  266. end
  267. 1 def public_key_hash
  268. Base64.encode64(OpenSSL::Digest::SHA256.new(serialized_public_key).to_s)
  269. end
  270. 1 def public_key
  271. 266 OpenSSL::PKey::RSA.new(serialized_public_key)
  272. rescue OpenSSL::PKey::RSAError
  273. 1 nil
  274. end
  275. 1 def exported_key
  276. serialized_public_key
  277. end
  278. # discovery (webfinger)
  279. 1 def self.find_or_fetch_by_identifier(diaspora_id)
  280. # exiting person?
  281. 791 person = by_account_identifier(diaspora_id)
  282. 791 return person if person.present? && person.profile.present?
  283. # create or update person from webfinger
  284. 11 logger.info "webfingering #{diaspora_id}, it is not known or needs updating"
  285. 11 DiasporaFederation::Discovery::Discovery.new(diaspora_id).fetch_and_save
  286. 5 by_account_identifier(diaspora_id)
  287. end
  288. 1 def self.by_account_identifier(diaspora_id)
  289. 1568 find_by(diaspora_handle: diaspora_id.strip.downcase)
  290. end
  291. 1 def remote?
  292. 9610 owner_id.nil?
  293. end
  294. 1 def local?
  295. 8797 !remote?
  296. end
  297. 1 def has_photos?
  298. 2 self.photos.exists?
  299. end
  300. 1 def as_json( opts = {} )
  301. 14 opts ||= {}
  302. json = {
  303. 14 id: id,
  304. guid: guid,
  305. name: name,
  306. avatar: profile.image_url(size: :thumb_small),
  307. handle: diaspora_handle,
  308. url: Rails.application.routes.url_helpers.person_path(self)
  309. }
  310. 16 json.merge!(:tags => self.profile.tags.map{|t| "##{t.name}"}) if opts[:includes] == "tags"
  311. 14 json
  312. end
  313. 1 def lock_access!
  314. 141 self.closed_account = true
  315. 141 self.save
  316. end
  317. 1 def clear_profile!
  318. 126 self.profile.tombstone!
  319. 126 self
  320. end
  321. 1 private
  322. 1 def fix_profile
  323. logger.info "fix profile for account: #{diaspora_handle}"
  324. DiasporaFederation::Discovery::Discovery.new(diaspora_handle).fetch_and_save
  325. reload
  326. end
  327. 1 def owner_xor_pod
  328. 11295 errors.add(:base, "Specify an owner or a pod, not both") unless owner.blank? ^ pod.blank?
  329. end
  330. 1 def other_person_with_same_guid
  331. 7113 diaspora_id = Person.where(guid: guid).where.not(diaspora_handle: diaspora_handle).pluck(:diaspora_handle).first
  332. 7113 errors.add(:base, "Person with same GUID already exists: #{diaspora_id}") if diaspora_id
  333. end
  334. end

app/models/photo.rb

91.36% lines covered

81 relevant lines. 74 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2009, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Photo < ApplicationRecord
  6. 1 include Diaspora::Federated::Base
  7. 1 include Diaspora::Shareable
  8. # NOTE API V1 to be extracted
  9. 1 acts_as_api
  10. 1 api_accessible :backbone do |t|
  11. 1 t.add :id
  12. 1 t.add :guid
  13. 1 t.add :created_at
  14. 1 t.add :author
  15. 1 t.add lambda { |photo|
  16. {
  17. 2 small: photo.url(:thumb_small),
  18. medium: photo.url(:thumb_medium),
  19. large: photo.url(:scaled_full),
  20. raw: photo.url
  21. }
  22. }, as: :sizes
  23. 1 t.add lambda { |photo|
  24. {
  25. 2 height: photo.height,
  26. width: photo.width
  27. }
  28. }, as: :dimensions
  29. 1 t.add lambda { |photo|
  30. {
  31. id: photo.status_message.id
  32. 2 } if photo.status_message
  33. }, as: :status_message
  34. end
  35. 1 mount_uploader :processed_image, ProcessedImage
  36. 1 mount_uploader :unprocessed_image, UnprocessedImage
  37. 1 attr_accessor :keep_original_format
  38. 1 belongs_to :status_message, foreign_key: :status_message_guid, primary_key: :guid, optional: true
  39. 1 validates_associated :status_message
  40. 1 delegate :author_name, to: :status_message, prefix: true
  41. 1 validate :ownership_of_status_message
  42. 1 before_destroy :ensure_user_picture
  43. 1 after_destroy :clear_empty_status_message
  44. 1 after_commit on: :create do
  45. 657 queue_processing_job if author.local?
  46. end
  47. 1 scope :on_statuses, ->(post_guids) {
  48. where(status_message_guid: post_guids)
  49. }
  50. 1 def clear_empty_status_message
  51. 17 if status_message&.text_and_photos_blank?
  52. 2 status_message.destroy
  53. else
  54. 15 true
  55. end
  56. end
  57. 1 def ownership_of_status_message
  58. 1809 message = StatusMessage.find_by(guid: status_message_guid)
  59. 1809 return unless status_message_guid && message && diaspora_handle != message.diaspora_handle
  60. errors.add(:base, "Photo must have the same owner as status message")
  61. end
  62. 1 def self.diaspora_initialize(params={})
  63. 617 photo = new(params.to_hash.stringify_keys.slice(*column_names, "author"))
  64. 617 photo.random_string = SecureRandom.hex(10)
  65. 617 if params[:user_file]
  66. 616 image_file = params.delete(:user_file)
  67. 616 photo.unprocessed_image.store! image_file
  68. 1 elsif params[:image_url]
  69. 1 photo.remote_unprocessed_image_url = params[:image_url]
  70. 1 photo.unprocessed_image.store!
  71. end
  72. 613 photo.update_remote_path
  73. 613 photo
  74. end
  75. 1 def processed?
  76. 487 processed_image.path.present?
  77. end
  78. 1 def update_remote_path
  79. 682 remote_path = if unprocessed_image.url.match(%r{^https?://})
  80. unprocessed_image.url
  81. else
  82. 682 "#{AppConfig.pod_uri.to_s.chomp('/')}#{unprocessed_image.url}"
  83. end
  84. 682 name_start = remote_path.rindex "/"
  85. 682 self.remote_photo_path = "#{remote_path.slice(0, name_start)}/"
  86. 682 self.remote_photo_name = remote_path.slice(name_start + 1, remote_path.length)
  87. end
  88. 1 def url(name=nil)
  89. 199 if remote_photo_path.present? && remote_photo_name.present?
  90. 199 name = "#{name}_" if name
  91. 199 image_url = remote_photo_path + name.to_s + remote_photo_name
  92. 199 camo_image_url(image_url)
  93. elsif processed?
  94. processed_image.url(name)
  95. else
  96. unprocessed_image.url(name)
  97. end
  98. end
  99. 1 def ensure_user_picture
  100. 17 profiles = Profile.where(image_url: url(:thumb_large))
  101. 17 profiles.each { |profile|
  102. 2 profile.image_url = nil
  103. 2 profile.save
  104. }
  105. end
  106. 1 def queue_processing_job
  107. 607 Workers::ProcessPhoto.perform_async(id)
  108. end
  109. 1 def self.visible(current_user, person, limit=:all, max_time=nil)
  110. 57 photos = if current_user
  111. 41 current_user.photos_from(person, limit: limit, max_time: max_time)
  112. else
  113. 16 Photo.where(author_id: person.id, public: true)
  114. end
  115. 57 photos.where(pending: false).order("created_at DESC")
  116. end
  117. 1 private
  118. 1 def camo_image_url(image_url)
  119. 199 if AppConfig.privacy.camo.proxy_remote_pod_images?
  120. Diaspora::Camo.image_url(image_url)
  121. else
  122. 199 image_url
  123. end
  124. end
  125. end

app/models/pod.rb

100.0% lines covered

74 relevant lines. 74 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Pod < ApplicationRecord
  3. # a pod is active if it is online or was online less than 14 days ago
  4. 1 ACTIVE_DAYS = 14.days
  5. 1 enum status: %i(
  6. unchecked
  7. no_errors
  8. dns_failed
  9. net_failed
  10. ssl_failed
  11. http_failed
  12. version_failed
  13. unknown_error
  14. )
  15. ERROR_MAP = {
  16. 1 ConnectionTester::AddressFailure => :dns_failed,
  17. ConnectionTester::DNSFailure => :dns_failed,
  18. ConnectionTester::NetFailure => :net_failed,
  19. ConnectionTester::SSLFailure => :ssl_failed,
  20. ConnectionTester::HTTPFailure => :http_failed,
  21. ConnectionTester::NodeInfoFailure => :version_failed
  22. }.freeze
  23. # this are only the most common errors, the rest will be +unknown_error+
  24. 1 CURL_ERROR_MAP = {
  25. couldnt_resolve_host: :dns_failed,
  26. couldnt_connect: :net_failed,
  27. operation_timedout: :net_failed,
  28. ssl_cipher: :ssl_failed,
  29. ssl_cacert: :ssl_failed,
  30. redirected_to_other_hostname: :http_failed
  31. }.freeze
  32. # use -1 as port for default ports
  33. # we can't use the real default port (80/443) because we need to handle them
  34. # like both are the same and not both can exist at the same time.
  35. # we also can't use nil, because databases don't handle NULL in unique indexes
  36. # (except postgres >= 15 with "NULLS NOT DISTINCT").
  37. 1 DEFAULT_PORT = -1
  38. 1 DEFAULT_PORTS = [URI::HTTP::DEFAULT_PORT, URI::HTTPS::DEFAULT_PORT].freeze
  39. 1 has_many :people
  40. 1 scope :check_failed, lambda {
  41. 2 where(arel_table[:status].gt(Pod.statuses[:no_errors])).where.not(status: Pod.statuses[:version_failed])
  42. }
  43. 1 scope :active, -> {
  44. 2 where(["offline_since is null or offline_since > ?", DateTime.now.utc - ACTIVE_DAYS])
  45. }
  46. 1 validate :not_own_pod
  47. 1 class << self
  48. 1 def find_or_create_by(opts) # Rename this method to not override an AR method
  49. 3380 uri = URI.parse(opts.fetch(:url))
  50. 3380 port = DEFAULT_PORTS.include?(uri.port) ? DEFAULT_PORT : uri.port
  51. 3380 find_or_initialize_by(host: uri.host.downcase, port: port).tap do |pod|
  52. 3380 pod.ssl ||= (uri.scheme == "https")
  53. 3380 pod.save
  54. end
  55. end
  56. # don't consider a failed version reading to be fatal
  57. 1 def offline_statuses
  58. 321 [Pod.statuses[:dns_failed],
  59. Pod.statuses[:net_failed],
  60. Pod.statuses[:ssl_failed],
  61. Pod.statuses[:http_failed],
  62. Pod.statuses[:unknown_error]]
  63. end
  64. 1 def check_all!
  65. 4 Pod.find_in_batches(batch_size: 20) {|batch| batch.each(&:test_connection!) }
  66. end
  67. 1 def check_scheduled!
  68. 2 Pod.where(scheduled_check: true).find_each(&:test_connection!)
  69. end
  70. end
  71. 1 def offline?
  72. 321 Pod.offline_statuses.include?(Pod.statuses[status])
  73. end
  74. # a pod is active if it is online or was online recently
  75. 1 def active?
  76. 204 !offline? || offline_since.try {|date| date > DateTime.now.utc - ACTIVE_DAYS }
  77. end
  78. 1 def to_s
  79. 1 "#{id}:#{host}"
  80. end
  81. 1 def schedule_check_if_needed
  82. 85 update_column(:scheduled_check, true) if offline? && !scheduled_check
  83. end
  84. 1 def test_connection!
  85. 6 result = ConnectionTester.check uri.to_s
  86. 6 logger.debug "tested pod: '#{uri}' - #{result.inspect}"
  87. 6 transaction do
  88. 6 update_from_result(result)
  89. end
  90. end
  91. # @param path [String]
  92. # @return [String]
  93. 1 def url_to(path)
  94. 630 uri.tap {|uri| uri.path = path }.to_s
  95. end
  96. 1 def update_offline_since
  97. 14 if offline?
  98. 9 self.offline_since ||= DateTime.now.utc
  99. else
  100. 5 self.offline_since = nil
  101. end
  102. end
  103. 1 private
  104. 1 def update_from_result(result)
  105. 6 self.status = status_from_result(result)
  106. 6 update_offline_since
  107. 6 logger.warn "#{uri} OFFLINE: #{result.failure_message}" if offline?
  108. 6 attributes_from_result(result)
  109. 6 touch(:checked_at)
  110. 6 self.scheduled_check = false
  111. 6 save
  112. end
  113. 1 def attributes_from_result(result)
  114. 6 self.ssl ||= result.ssl
  115. 6 self.error = result.error? ? result.failure_message[0..254] : nil
  116. 6 self.software = result.software_version[0..254] if result.software_version.present?
  117. 6 self.response_time = result.rt
  118. end
  119. 1 def status_from_result(result)
  120. 6 if result.error?
  121. 4 ERROR_MAP.fetch(result.error.class, :unknown_error)
  122. else
  123. 2 :no_errors
  124. end
  125. end
  126. # @return [URI]
  127. 1 def uri
  128. 331 @uri ||= (ssl ? URI::HTTPS : URI::HTTP).build(host: host, port: real_port)
  129. 331 @uri.dup
  130. end
  131. 1 def real_port
  132. 311 if port == DEFAULT_PORT
  133. 309 ssl ? URI::HTTPS::DEFAULT_PORT : URI::HTTP::DEFAULT_PORT
  134. else
  135. 2 port
  136. end
  137. end
  138. 1 def not_own_pod
  139. 3447 pod_uri = AppConfig.pod_uri
  140. 3447 pod_port = DEFAULT_PORTS.include?(pod_uri.port) ? DEFAULT_PORT : pod_uri.port
  141. 3447 errors.add(:base, "own pod not allowed") if pod_uri.host.downcase == host && pod_port == port
  142. end
  143. end

app/models/poll.rb

95.24% lines covered

21 relevant lines. 20 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Poll < ApplicationRecord
  3. 1 include Diaspora::Federated::Base
  4. 1 include Diaspora::Fields::Guid
  5. 1 include Diaspora::Federated::Fetchable
  6. 1 belongs_to :status_message
  7. 373 has_many :poll_answers, -> { order "id ASC" }, dependent: :destroy
  8. 1 has_many :poll_participations, dependent: :destroy
  9. 1 has_one :author, through: :status_message
  10. #forward some requests to status message, because a poll is just attached to a status message and is not sharable itself
  11. 1 delegate :author_id, :diaspora_handle, :public?, :subscribers, to: :status_message
  12. 1 validate :enough_poll_answers
  13. 1 validates :question, presence: true
  14. 3 scope :all_public, -> { joins(:status_message).where(posts: {public: true}) }
  15. 1 self.include_root_in_json = false
  16. 1 def enough_poll_answers
  17. 271 errors.add(:poll_answers, I18n.t("activerecord.errors.models.poll.attributes.poll_answers.not_enough_poll_answers")) if poll_answers.size < 2
  18. end
  19. 1 def as_json(options={})
  20. {
  21. poll_id: id,
  22. post_id: status_message.id,
  23. question: question,
  24. poll_answers: poll_answers,
  25. participation_count: participation_count
  26. }
  27. end
  28. 1 def participation_answer(user)
  29. 9 poll_participations.find_by(author_id: user.person.id)
  30. end
  31. 1 def participation_count
  32. 21 poll_answers.sum("vote_count")
  33. end
  34. end

app/models/poll_answer.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PollAnswer < ApplicationRecord
  3. 1 include Diaspora::Federated::Base
  4. 1 include Diaspora::Fields::Guid
  5. 1 belongs_to :poll
  6. 1 has_many :poll_participations
  7. 1 validates :answer, presence: true
  8. 1 self.include_root_in_json = false
  9. end

app/models/poll_participation.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PollParticipation < ApplicationRecord
  3. 1 include Diaspora::Federated::Base
  4. 1 include Diaspora::Fields::Guid
  5. 1 include Diaspora::Fields::Author
  6. 1 include Diaspora::Relayable
  7. 1 belongs_to :poll
  8. 1 belongs_to :poll_answer, counter_cache: :vote_count
  9. 1 has_one :status_message, through: :poll
  10. 1 has_one :signature, class_name: "PollParticipationSignature", dependent: :delete
  11. 1 alias_attribute :parent, :poll
  12. 1 validate :not_already_participated
  13. 1 def poll_answer_guid=(new_poll_answer_guid)
  14. 20 self.poll_answer_id = PollAnswer.where(guid: new_poll_answer_guid).ids.first
  15. end
  16. 1 def not_already_participated
  17. 80 return if poll.nil?
  18. 79 other_participations = PollParticipation.where(author_id: self.author.id, poll_id: self.poll.id).to_a-[self]
  19. 79 if other_participations.present?
  20. 4 self.errors.add(:poll, I18n.t("activerecord.errors.models.poll_participation.attributes.poll.already_participated"))
  21. end
  22. end
  23. 1 class Generator < Diaspora::Federated::Generator
  24. 1 def self.federated_class
  25. 31 PollParticipation
  26. end
  27. 1 def initialize(person, target, poll_answer)
  28. 31 @poll_answer = poll_answer
  29. 31 super(person, target)
  30. end
  31. 1 def relayable_options
  32. 31 {:poll => @target.poll, :poll_answer => @poll_answer}
  33. end
  34. end
  35. end

app/models/poll_participation_signature.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PollParticipationSignature < ApplicationRecord
  3. 1 include Diaspora::Signature
  4. 1 self.primary_key = :poll_participation_id
  5. 1 belongs_to :poll_participation
  6. end

app/models/post.rb

98.73% lines covered

79 relevant lines. 78 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Post < ApplicationRecord
  6. 1 self.include_root_in_json = false
  7. 1 include ApplicationHelper
  8. 1 include Diaspora::Federated::Base
  9. 1 include Diaspora::Federated::Fetchable
  10. 1 include Diaspora::Likeable
  11. 1 include Diaspora::Commentable
  12. 1 include Diaspora::Shareable
  13. 1 include Diaspora::MentionsContainer
  14. 1 has_many :participations, dependent: :delete_all, as: :target, inverse_of: :target
  15. 1 has_many :participants, through: :participations, source: :author
  16. 1 attr_accessor :user_like
  17. 1 has_many :reports, as: :item
  18. 1 has_many :reshares, class_name: "Reshare", foreign_key: :root_guid, primary_key: :guid
  19. 1 has_many :resharers, class_name: "Person", through: :reshares, source: :author
  20. 1 belongs_to :o_embed_cache, optional: true
  21. 1 belongs_to :open_graph_cache, optional: true
  22. 1 validates_uniqueness_of :id
  23. 1 after_create do
  24. 2611 self.touch(:interacted_at)
  25. end
  26. 1 before_destroy do
  27. 29 reshares.update_all(root_guid: nil) # rubocop:disable Rails/SkipsModelValidations
  28. end
  29. #scopes
  30. 1 scope :includes_for_a_stream, -> {
  31. 136 includes(:o_embed_cache,
  32. :open_graph_cache,
  33. {:author => :profile},
  34. :mentions => {:person => :profile}
  35. ) #note should include root and photos, but i think those are both on status_message
  36. }
  37. 49 scope :all_public, -> { where(public: true) }
  38. 1 scope :all_local_public, -> {
  39. 4 where(" exists (
  40. select 1 from people where posts.author_id = people.id
  41. and people.pod_id is null)
  42. and posts.public = true")
  43. }
  44. 1 scope :commented_by, ->(person) {
  45. 5 select('DISTINCT posts.*')
  46. .joins(:comments)
  47. .where(:comments => {:author_id => person.id})
  48. }
  49. 1 scope :liked_by, ->(person) {
  50. 5 joins(:likes).where(:likes => {:author_id => person.id})
  51. }
  52. 1 scope :subscribed_by, ->(user) {
  53. 36 joins(:participations).where(participations: {author_id: user.person_id})
  54. }
  55. 2 scope :reshares, -> { where(type: "Reshare") }
  56. 1 scope :reshared_by, ->(person) {
  57. # we join on the same table, Rails renames "posts" to "reshares_posts" for the right table
  58. 2 joins(:reshares).where(reshares_posts: {author_id: person.id})
  59. }
  60. 1 def post_type
  61. 180 self.class.name
  62. end
  63. 1 def root; end
  64. 2 def photos; []; end
  65. #prevents error when trying to access @post.address in a post different than Reshare and StatusMessage types;
  66. #check PostPresenter
  67. 1 def address
  68. end
  69. 1 def poll
  70. end
  71. 1 def self.excluding_blocks(user)
  72. 90 people = user.blocks.map{|b| b.person_id}
  73. 84 scope = all
  74. 84 if people.any?
  75. 6 scope = scope.where("posts.author_id NOT IN (?)", people)
  76. end
  77. 84 scope
  78. end
  79. 1 def self.excluding_hidden_shareables(user)
  80. 95 scope = all
  81. 95 if user.has_hidden_shareables_of_type?
  82. 2 scope = scope.where('posts.id NOT IN (?)', user.hidden_shareables["#{self.base_class}"])
  83. end
  84. 95 scope
  85. end
  86. 1 def self.excluding_hidden_content(user)
  87. 83 excluding_blocks(user).excluding_hidden_shareables(user)
  88. end
  89. 1 def self.for_a_stream(max_time, order, user=nil, ignore_blocks=false)
  90. 137 scope = self.for_visible_shareable_sql(max_time, order).
  91. includes_for_a_stream
  92. 137 if user.present?
  93. 73 if ignore_blocks
  94. 11 scope = scope.excluding_hidden_shareables(user)
  95. else
  96. 62 scope = scope.excluding_hidden_content(user)
  97. end
  98. end
  99. 137 scope
  100. end
  101. 1 def reshare_for(user)
  102. 88 return unless user
  103. 66 reshares.find_by(author_id: user.person.id)
  104. end
  105. 1 def like_for(user)
  106. 88 return unless user
  107. 66 likes.find_by(author_id: user.person.id)
  108. end
  109. #############
  110. # @return [Integer]
  111. 1 def update_reshares_counter
  112. 155 self.class.where(id: id).update_all(reshares_count: reshares.count)
  113. end
  114. 1 def self.diaspora_initialize(params)
  115. 1331 new(params.to_hash.stringify_keys.slice(*column_names, "author"))
  116. end
  117. 1 def comment_email_subject
  118. I18n.t('notifier.a_post_you_shared')
  119. end
  120. 1 def nsfw
  121. 197 self.author.profile.nsfw?
  122. end
  123. 1 def subscribers
  124. 1342 super.tap do |subscribers|
  125. 1342 subscribers.concat(resharers).concat(participants) if public?
  126. end
  127. end
  128. end

app/models/profile.rb

97.92% lines covered

96 relevant lines. 94 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Profile < ApplicationRecord
  6. 1 MAX_TAGS = 5
  7. 1 self.include_root_in_json = false
  8. 1 include Diaspora::Federated::Base
  9. 1 include Diaspora::Taggable
  10. 1 attr_accessor :tag_string
  11. 1 acts_as_ordered_taggable
  12. 1 extract_tags_from :tag_string
  13. 1 validates :tag_list, :length => { :maximum => 5 }
  14. 1 before_save :strip_names
  15. 1 after_validation :strip_names
  16. 1 validates :first_name, :length => { :maximum => 32 }
  17. 1 validates :last_name, :length => { :maximum => 32 }
  18. 1 validates :location, :length => { :maximum =>255 }
  19. 1 validates :gender, length: {maximum: 255}
  20. 1 validates_format_of :first_name, :with => /\A[^;]+\z/, :allow_blank => true
  21. 1 validates_format_of :last_name, :with => /\A[^;]+\z/, :allow_blank => true
  22. 1 validate :max_tags
  23. 1 validate :valid_birthday
  24. 1 belongs_to :person
  25. 1 before_validation do
  26. 12793 self.tag_string = self.tag_string.split[0..4].join(' ')
  27. 12793 self.build_tags
  28. end
  29. 1 before_save do
  30. 10562 self.build_tags
  31. 10562 self.construct_full_name
  32. end
  33. 1 def subscribers
  34. 45 Person.joins(:contacts).where(contacts: {user_id: person.owner_id})
  35. end
  36. 1 def public?
  37. 46 public_details?
  38. end
  39. 1 def diaspora_handle
  40. #get the parent diaspora handle, unless we want to access a profile without a person
  41. 146 (self.person) ? self.person.diaspora_handle : self[:diaspora_handle]
  42. end
  43. 1 def image_url(size: :thumb_large, fallback_to_default: true)
  44. 3077 result = if size == :thumb_medium && self[:image_url_medium]
  45. 321 self[:image_url_medium]
  46. 2756 elsif size == :thumb_small && self[:image_url_small]
  47. 225 self[:image_url_small]
  48. else
  49. 2531 self[:image_url]
  50. end
  51. 3077 if result
  52. 616 if AppConfig.privacy.camo.proxy_remote_pod_images?
  53. Diaspora::Camo.image_url(result)
  54. else
  55. 616 result
  56. end
  57. 2461 elsif fallback_to_default
  58. 2369 ActionController::Base.helpers.image_path("user/default.png")
  59. end
  60. end
  61. 1 def from_omniauth_hash(omniauth_user_hash)
  62. 7 mappings = {"description" => "bio",
  63. 'image' => 'image_url',
  64. 'name' => 'first_name',
  65. 'location' => 'location',
  66. }
  67. 27 update_hash = Hash[ omniauth_user_hash.map {|k, v| [mappings[k], v] } ]
  68. 19 self.attributes.merge(update_hash){|key, old, new| old.blank? ? new : old}
  69. end
  70. 1 def image_url=(url)
  71. 704 super(build_image_url(url))
  72. end
  73. 1 def image_url_small=(url)
  74. 695 super(build_image_url(url))
  75. end
  76. 1 def image_url_medium=(url)
  77. 695 super(build_image_url(url))
  78. end
  79. 1 def date= params
  80. 21 if %w(month day).all? {|key| params[key].present? }
  81. 5 params["year"] = "1004" if params["year"].blank?
  82. 5 if Date.valid_civil?(params["year"].to_i, params["month"].to_i, params["day"].to_i)
  83. 2 self.birthday = Date.new(params["year"].to_i, params["month"].to_i, params["day"].to_i)
  84. else
  85. 3 @invalid_birthday_date = true
  86. end
  87. 8 elsif %w(year month day).all? {|key| params[key].blank? }
  88. 1 self.birthday = nil
  89. end
  90. end
  91. 1 def bio_message
  92. 58 @bio_message ||= Diaspora::MessageRenderer.new(bio)
  93. end
  94. 1 def location_message
  95. 58 @location_message ||= Diaspora::MessageRenderer.new(location)
  96. end
  97. 1 def tag_string
  98. 62253 @tag_string ||= tags.pluck(:name).map {|tag| "##{tag}" }.join(" ")
  99. end
  100. # Constructs a full name by joining #first_name and #last_name
  101. # @return [String] A full name
  102. 1 def construct_full_name
  103. 10562 self.full_name = [self.first_name, self.last_name].join(' ').downcase.strip
  104. 10562 self.full_name
  105. end
  106. 1 def tombstone!
  107. 128 @tag_string = nil
  108. 128 self.taggings.delete_all
  109. 128 clearable_fields.each do |field|
  110. 1792 self[field] = nil
  111. end
  112. 128 self[:searchable] = false
  113. 128 self.save
  114. end
  115. 1 protected
  116. 1 def strip_names
  117. 23355 self.first_name.strip! if self.first_name
  118. 23355 self.last_name.strip! if self.last_name
  119. end
  120. 1 def max_tags
  121. 12793 if self.tag_string.count('#') > 5
  122. errors[:base] << 'Profile cannot have more than five tags'
  123. end
  124. end
  125. 1 def valid_birthday
  126. 12793 if @invalid_birthday_date
  127. 1 errors.add(:birthday)
  128. 1 @invalid_birthday_date = nil
  129. end
  130. end
  131. 1 private
  132. 1 def clearable_fields
  133. 130 attributes.keys - %w[id created_at updated_at person_id tag_list]
  134. end
  135. 1 def build_image_url(url)
  136. 2094 return nil if url.blank? || url.match(/user\/default/)
  137. 1973 return url if url.match(/^https?:\/\//)
  138. 4 "#{AppConfig.pod_uri.to_s.chomp('/')}#{url}"
  139. end
  140. end

app/models/reference.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Reference < ApplicationRecord
  3. 1 belongs_to :source, polymorphic: true
  4. 1 belongs_to :target, polymorphic: true
  5. 1 validates :target_id, uniqueness: {scope: %i[target_type source_id source_type]}
  6. 1 module Source
  7. 1 extend ActiveSupport::Concern
  8. 1 included do
  9. 3 after_create :create_references
  10. 3 has_many :references, as: :source, dependent: :destroy
  11. end
  12. 1 def create_references
  13. 3256 text&.scan(DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX)&.each do |author, type, guid|
  14. 24 add_reference(author, type, guid)
  15. end
  16. end
  17. 1 private
  18. 1 def add_reference(author, type, guid)
  19. 24 entity = Diaspora::EntityFinder.new(type, guid).find
  20. 21 references.find_or_create_by(target: entity) if entity&.diaspora_handle == author
  21. rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
  22. 3 logger.warn "ignoring invalid diaspora-url: diaspora://#{author}/#{type}/#{guid}: #{e.class}: #{e.message}"
  23. end
  24. end
  25. 1 module Target
  26. 1 extend ActiveSupport::Concern
  27. 1 included do
  28. 1 has_many :referenced_by, as: :target, class_name: "Reference", dependent: :destroy
  29. end
  30. end
  31. end

app/models/report.rb

85.71% lines covered

35 relevant lines. 30 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Report < ApplicationRecord
  3. 1 validates :user_id, presence: true
  4. 1 validates :item_id, presence: true
  5. 1 validates :item_type, presence: true, inclusion: {
  6. in: %w(Post Comment), message: "Type should match `Post` or `Comment`!"}
  7. 1 validates :text, presence: true
  8. 1 validate :entry_does_not_exist, :on => :create
  9. 1 validate :post_or_comment_does_exist, :on => :create
  10. 1 belongs_to :user
  11. 1 belongs_to :post, optional: true
  12. 1 belongs_to :comment, optional: true
  13. 1 belongs_to :item, polymorphic: true
  14. 1 after_commit :send_report_notification, :on => :create
  15. 1 def reported_author
  16. item.author if item
  17. end
  18. 1 def entry_does_not_exist
  19. 50 return unless Report.where(item_id: item_id, item_type: item_type).exists?(user_id: user_id)
  20. 3 errors.add(:base, "You cannot report the same post twice.")
  21. end
  22. 1 def post_or_comment_does_exist
  23. 50 return unless Post.find_by(id: item_id).nil? && Comment.find_by(id: item_id).nil?
  24. 3 errors.add(:base, "Post or comment was already deleted or doesn't exists.")
  25. end
  26. 1 def destroy_reported_item
  27. 4 case item
  28. when Post
  29. 2 if item.author.local?
  30. 2 item.author.owner.retract(item)
  31. else
  32. item.destroy
  33. end
  34. when Comment
  35. 2 if item.author.local?
  36. 2 item.author.owner.retract(item)
  37. elsif item.parent.author.local?
  38. item.parent.author.owner.retract(item)
  39. else
  40. item.destroy
  41. end
  42. end
  43. 4 mark_as_reviewed
  44. end
  45. 1 def mark_as_reviewed
  46. 6 Report.where(item_id: item_id, item_type: item_type).update_all(reviewed: true)
  47. end
  48. 1 def send_report_notification
  49. 42 Workers::Mail::ReportWorker.perform_async(id)
  50. end
  51. end

app/models/reshare.rb

97.78% lines covered

45 relevant lines. 44 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Reshare < Post
  6. 1 belongs_to :root, class_name: "Post", foreign_key: :root_guid, primary_key: :guid, optional: true
  7. 1 validate :root_must_be_public
  8. 168 validates :root, presence: true, on: :create, if: proc {|reshare| reshare.author.local? }
  9. 1 validates :root_guid, uniqueness: {scope: :author_id}, allow_nil: true
  10. 1 delegate :author, to: :root, prefix: true
  11. 1 before_validation do
  12. 187 self.public = true
  13. end
  14. 1 after_commit :on => :create do
  15. 155 self.root.update_reshares_counter if self.root.present?
  16. end
  17. 1 after_destroy do
  18. self.root.update_reshares_counter if self.root.present?
  19. end
  20. 1 acts_as_api
  21. 1 api_accessible :backbone do |t|
  22. 1 t.add :id
  23. 1 t.add :guid
  24. 1 t.add :author
  25. 1 t.add :created_at
  26. end
  27. 1 def root_diaspora_id
  28. 45 root.try(:author).try(:diaspora_handle)
  29. end
  30. 1 delegate :o_embed_cache, :open_graph_cache,
  31. :message, :nsfw,
  32. to: :absolute_root, allow_nil: true
  33. 1 def text
  34. 156 absolute_root.try(:text) || ""
  35. end
  36. 1 def mentioned_people
  37. 20 absolute_root.try(:mentioned_people) || super
  38. end
  39. 1 def photos
  40. 20 absolute_root.try(:photos) || super
  41. end
  42. 1 def post_location
  43. {
  44. 22 address: absolute_root.try(:location).try(:address),
  45. lat: absolute_root.try(:location).try(:lat),
  46. lng: absolute_root.try(:location).try(:lng)
  47. }
  48. end
  49. 1 def poll
  50. 32 absolute_root.try(:poll) || super
  51. end
  52. 1 def comment_email_subject
  53. 1 I18n.t('reshares.comment_email_subject', :resharer => author.name, :author => root.author_name)
  54. end
  55. 1 def absolute_root
  56. 482 @absolute_root ||= self
  57. 482 @absolute_root = @absolute_root.root while @absolute_root.is_a? Reshare
  58. 482 @absolute_root
  59. end
  60. 1 def receive(recipient_user_ids)
  61. 16 super(recipient_user_ids)
  62. 16 root.author.owner.participate!(self) if root.author.local?
  63. end
  64. 1 def subscribers
  65. 56 super.tap {|people| root.try {|root| people << root.author } }
  66. end
  67. 1 private
  68. 1 def root_must_be_public
  69. 187 errors.add(:base, "Only posts which are public may be reshared.") if root && !root.public
  70. end
  71. end

app/models/role.rb

88.46% lines covered

26 relevant lines. 23 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. # NOTE add the person object you want to attach role to...
  3. 1 class Role < ApplicationRecord
  4. 1 belongs_to :person
  5. 1 validates :name, uniqueness: {scope: :person_id}
  6. 1 validates :name, inclusion: {in: %w(admin moderator spotlight)}
  7. 114 scope :admins, -> { where(name: "admin") }
  8. 273 scope :moderators, -> { where(name: %w(moderator admin)) }
  9. 1 def self.is_admin?(person)
  10. 337 exists?(person_id: person.id, name: "admin")
  11. end
  12. 1 def self.add_admin(person)
  13. 39 find_or_create_by(person_id: person.id, name: "admin")
  14. end
  15. 1 def self.remove_admin(person)
  16. find_by(person_id: person.id, name: "admin").destroy
  17. end
  18. 1 def self.moderator?(person)
  19. 263 moderators.exists?(person_id: person.id)
  20. end
  21. 1 def self.moderator_only?(person)
  22. 20 exists?(person_id: person.id, name: "moderator")
  23. end
  24. 1 def self.add_moderator(person)
  25. 17 find_or_create_by(person_id: person.id, name: "moderator")
  26. end
  27. 1 def self.remove_moderator(person)
  28. find_by(person_id: person.id, name: "moderator").destroy
  29. end
  30. 1 def self.spotlight?(person)
  31. 20 exists?(person_id: person.id, name: "spotlight")
  32. end
  33. 1 def self.add_spotlight(person)
  34. 3 find_or_create_by(person_id: person.id, name: "spotlight")
  35. end
  36. 1 def self.remove_spotlight(person)
  37. find_by(person_id: person.id, name: "spotlight").destroy
  38. end
  39. end

app/models/service.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class Service < ApplicationRecord
  6. 1 attr_accessor :provider, :info, :access_level
  7. 1 belongs_to :user
  8. 1 validates_uniqueness_of :uid, :scope => :type
  9. 1 def profile_photo_url
  10. nil
  11. end
  12. 1 def post_opts(post)
  13. # don't do anything (should be overridden by service extensions)
  14. end
  15. 1 class << self
  16. 1 def titles(service_strings)
  17. 31 service_strings.map {|s| "Services::#{s.titleize}"}
  18. end
  19. 1 def first_from_omniauth( auth_hash )
  20. 1 @@auth = auth_hash
  21. 1 find_by(type: service_type, uid: options[:uid])
  22. end
  23. 1 def initialize_from_omniauth( auth_hash )
  24. 6 @@auth = auth_hash
  25. 6 service_type.constantize.new( options )
  26. end
  27. 1 def auth
  28. 84 @@auth
  29. end
  30. 1 def service_type
  31. 7 "Services::#{options[:provider].camelize}"
  32. end
  33. 1 def options
  34. {
  35. 14 nickname: auth['info']['nickname'],
  36. access_token: auth['credentials']['token'],
  37. access_secret: auth['credentials']['secret'],
  38. uid: auth['uid'],
  39. provider: auth['provider'],
  40. info: auth['info']
  41. }
  42. end
  43. 1 private :auth, :service_type, :options
  44. end
  45. end

app/models/services/tumblr.rb

100.0% lines covered

43 relevant lines. 43 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Services
  3. 1 class Tumblr < Service
  4. 1 MAX_CHARACTERS = 1000
  5. 1 def provider
  6. 11 "tumblr"
  7. end
  8. 1 def post(post, url="") # rubocop:disable Metrics/AbcSize
  9. 2 return true if post.nil? # return if post is deleted while waiting in queue
  10. 2 body = build_tumblr_post(post, url)
  11. 2 user_info = JSON.parse(client.get("/v2/user/info").body)
  12. 2 blogs = user_info["response"]["user"]["blogs"]
  13. 5 primaryblog = blogs.find {|blog| blog["primary"] } || blogs[0]
  14. 2 tumblr_ids = {}
  15. 2 blogurl = URI.parse(primaryblog["url"])
  16. 2 tumblr_ids[blogurl.host.to_s] = request_to_external_blog(blogurl, body)
  17. 2 post.tumblr_ids = tumblr_ids.to_json
  18. 2 post.save
  19. end
  20. 1 def post_opts(post)
  21. 2 {tumblr_ids: post.tumblr_ids} if post.tumblr_ids.present?
  22. end
  23. 1 def delete_from_service(opts)
  24. 1 logger.debug "event=delete_from_service type=tumblr sender_id=#{user_id} tumblr_ids=#{opts[:tumblr_ids]}"
  25. 1 tumblr_posts = JSON.parse(opts[:tumblr_ids])
  26. 1 tumblr_posts.each do |blog_name, post_id|
  27. 1 delete_from_tumblr(blog_name, post_id)
  28. end
  29. end
  30. 1 def build_tumblr_post(post, url)
  31. 4 {type: "text", format: "markdown", body: tumblr_template(post, url), tags: tags(post), native_inline_images: true}
  32. end
  33. 1 private
  34. 1 def client
  35. 5 @consumer ||= OAuth::Consumer.new(consumer_key, consumer_secret, site: "https://api.tumblr.com")
  36. 5 @client ||= OAuth::AccessToken.new(@consumer, access_token, access_secret)
  37. end
  38. 1 def tumblr_template(post, url)
  39. 4 photo_html = post.photos.map {|photo| "![photo](#{photo.url(:scaled_full)})\n\n" }.join
  40. 4 "#{photo_html}#{post.message.html(mentioned_people: [])}\n\n[original post](#{url})"
  41. end
  42. 1 def tags(post)
  43. 4 post.tags.pluck(:name).join(",").to_s
  44. end
  45. 1 def delete_from_tumblr(blog_name, service_post_id)
  46. 1 client.post("/v2/blog/#{blog_name}/post/delete", "id" => service_post_id)
  47. end
  48. 1 def request_to_external_blog(blogurl, body)
  49. 2 resp = client.post("/v2/blog/#{blogurl.host}/post", body)
  50. 2 JSON.parse(resp.body)["response"]["id"] if resp.code == "201"
  51. end
  52. 1 def consumer_key
  53. 3 AppConfig.services.tumblr.key
  54. end
  55. 1 def consumer_secret
  56. 3 AppConfig.services.tumblr.secret
  57. end
  58. end
  59. end

app/models/services/twitter.rb

82.76% lines covered

58 relevant lines. 48 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class Services::Twitter < Service
  3. 1 include Rails.application.routes.url_helpers
  4. 1 MAX_CHARACTERS = 280
  5. 1 SHORTENED_URL_LENGTH = 23
  6. 1 LINK_PATTERN = %r{https?://\S+}
  7. 1 def provider
  8. 15 "twitter"
  9. end
  10. 1 def post post, url=''
  11. 3 logger.debug "event=post_to_service type=twitter sender_id=#{user_id} post=#{post.guid}"
  12. 3 tweet = attempt_post post
  13. 3 post.tweet_id = tweet.id
  14. 3 post.save
  15. end
  16. 1 def profile_photo_url
  17. 1 client.user(nickname).profile_image_url_https "original"
  18. end
  19. 1 def post_opts(post)
  20. 3 {tweet_id: post.tweet_id} if post.tweet_id.present?
  21. end
  22. 1 def delete_from_service(opts)
  23. logger.debug "event=delete_from_service type=twitter sender_id=#{user_id} tweet_id=#{opts[:tweet_id]}"
  24. delete_from_twitter(opts[:tweet_id])
  25. end
  26. 1 private
  27. 1 def client
  28. 4 @client ||= Twitter::REST::Client.new do |config|
  29. 4 config.consumer_key = AppConfig.services.twitter.key
  30. 4 config.consumer_secret = AppConfig.services.twitter.secret
  31. 4 config.access_token = access_token
  32. 4 config.access_token_secret = access_secret
  33. end
  34. end
  35. 1 def attempt_post post, retry_count=0
  36. 3 message = build_twitter_post post, retry_count
  37. 3 client.update message
  38. rescue Twitter::Error::Forbidden => e
  39. if ! e.message.include? 'is over 140' || retry_count == 20
  40. raise e
  41. else
  42. attempt_post post, retry_count+1
  43. end
  44. end
  45. 1 def build_twitter_post post, retry_count=0
  46. 12 max_characters = MAX_CHARACTERS - retry_count
  47. 12 post_text = post.message.plain_text_without_markdown
  48. 12 truncate_and_add_post_link post, post_text, max_characters
  49. end
  50. 1 def truncate_and_add_post_link post, post_text, max_characters
  51. 12 return post_text unless needs_link? post, post_text, max_characters
  52. 5 post_url = short_post_url(
  53. post,
  54. protocol: AppConfig.pod_uri.scheme,
  55. host: AppConfig.pod_uri.authority
  56. )
  57. 5 truncated_text = post_text.truncate max_characters - SHORTENED_URL_LENGTH - 1
  58. 5 truncated_text = restore_truncated_url truncated_text, post_text, max_characters
  59. 5 "#{truncated_text} #{post_url}"
  60. end
  61. 1 def needs_link? post, post_text, max_characters
  62. 12 adjust_length_for_urls(post_text) > max_characters || post.photos.any?
  63. end
  64. 1 def adjust_length_for_urls post_text
  65. 12 real_length = post_text.length
  66. 12 URI.extract(post_text, ['http','https']) do |url|
  67. # add or subtract from real length - urls for tweets are always
  68. # shortened to SHORTENED_URL_LENGTH
  69. 4 if url.length >= SHORTENED_URL_LENGTH
  70. 4 real_length -= url.length - SHORTENED_URL_LENGTH
  71. else
  72. real_length += SHORTENED_URL_LENGTH - url.length
  73. end
  74. end
  75. 12 real_length
  76. end
  77. 1 def restore_truncated_url truncated_text, post_text, max_characters
  78. 5 return truncated_text if truncated_text !~ /#{LINK_PATTERN}\Z/
  79. url = post_text.match(LINK_PATTERN, truncated_text.rindex('http'))[0]
  80. truncated_text = post_text.truncate(
  81. max_characters - SHORTENED_URL_LENGTH + 2,
  82. separator: ' ', omission: ''
  83. )
  84. "#{truncated_text} #{url} ..."
  85. end
  86. 1 def delete_from_twitter service_post_id
  87. client.destroy_status service_post_id
  88. end
  89. end

app/models/services/wordpress.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Services
  3. 1 class Wordpress < Service
  4. 1 MAX_CHARACTERS = 1000
  5. 1 attr_accessor :username, :password, :host, :path
  6. # uid = blog_id
  7. 1 def provider
  8. 1 "wordpress"
  9. end
  10. 1 def post(post, _url="")
  11. 1 res = Faraday.new(url: "https://public-api.wordpress.com").post do |req|
  12. 1 req.url "/rest/v1/sites/#{uid}/posts/new"
  13. 1 req.body = post_body(post).to_json
  14. 1 req.headers["Authorization"] = "Bearer #{access_token}"
  15. 1 req.headers["Content-Type"] = "application/json"
  16. end
  17. 1 JSON.parse res.env[:body]
  18. end
  19. 1 def post_body(post)
  20. {
  21. 3 title: post.message.title,
  22. content: post.message.markdownified(disable_hovercards: true)
  23. }
  24. end
  25. end
  26. end

app/models/share_visibility.rb

86.96% lines covered

23 relevant lines. 20 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class ShareVisibility < ApplicationRecord
  6. 1 belongs_to :user
  7. 1 belongs_to :shareable, polymorphic: :true
  8. 1 scope :for_a_user, ->(user) {
  9. 33 where(user_id: user.id)
  10. }
  11. 1 scope :for_shareable, ->(shareable) {
  12. 799 where(shareable_id: shareable.id, shareable_type: shareable.class.base_class.to_s)
  13. }
  14. 1 validate :not_public
  15. # Perform a batch import, given a set of users and a shareable
  16. # @note performs a bulk insert in mySQL; performs linear insertions in postgres
  17. # @param user_ids [Array<Integer>] Recipients
  18. # @param share [Shareable]
  19. # @return [void]
  20. 1 def self.batch_import(user_ids, share)
  21. 801 return false if share.public?
  22. 799 user_ids -= ShareVisibility.for_shareable(share).where(user_id: user_ids).pluck(:user_id)
  23. 799 return false if user_ids.empty?
  24. 797 create_visilities(user_ids, share)
  25. end
  26. 1 private
  27. 1 private_class_method def self.create_visilities(user_ids, share)
  28. 797 if AppConfig.postgres?
  29. 797 user_ids.each do |user_id|
  30. 1047 ShareVisibility.find_or_create_by(
  31. user_id: user_id,
  32. shareable_id: share.id,
  33. shareable_type: share.class.base_class.to_s
  34. )
  35. end
  36. else
  37. new_share_visibilities_data = user_ids.map do |user_id|
  38. [user_id, share.id, share.class.base_class.to_s]
  39. end
  40. ShareVisibility.import(%i(user_id shareable_id shareable_type), new_share_visibilities_data)
  41. end
  42. end
  43. 1 def not_public
  44. 1063 errors[:base] << "Cannot create visibility for a public object" if shareable.public?
  45. end
  46. end

app/models/signature_order.rb

100.0% lines covered

2 relevant lines. 2 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class SignatureOrder < ApplicationRecord
  3. 1 validates :order, presence: true, uniqueness: true
  4. end

app/models/status_message.rb

98.48% lines covered

66 relevant lines. 65 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class StatusMessage < Post
  6. 1 include Diaspora::Taggable
  7. 1 include Reference::Source
  8. 1 include Reference::Target
  9. 1 include PeopleHelper
  10. 1 acts_as_taggable_on :tags
  11. 1 extract_tags_from :text
  12. 3 validates_length_of :text, :maximum => 65535, :message => proc {|p, v| I18n.t('status_messages.too_long', :count => 65535, :current_length => v[:value].length)}
  13. # don't allow creation of empty status messages
  14. 1 validate :presence_of_content, on: :create
  15. 1 has_many :photos, :dependent => :destroy, :foreign_key => :status_message_guid, :primary_key => :guid
  16. 1 has_one :location
  17. 1 has_one :poll, autosave: true, dependent: :destroy
  18. 1 has_many :poll_participations, through: :poll
  19. 1 attr_accessor :oembed_url
  20. 1 attr_accessor :open_graph_url
  21. 1 after_commit :queue_gather_oembed_data, :on => :create, :if => :contains_oembed_url_in_text?
  22. 1 after_commit :queue_gather_open_graph_data, :on => :create, :if => :contains_open_graph_url_in_text?
  23. #scopes
  24. 1 scope :where_person_is_mentioned, ->(person) {
  25. 33 owned_or_visible_by_user(person.owner).joins(:mentions).where(mentions: {person_id: person.id})
  26. }
  27. 1 def self.model_name
  28. 28 Post.model_name
  29. end
  30. 1 def self.guids_for_author(person)
  31. 6 Post.connection.select_values(Post.where(:author_id => person.id).select('posts.guid').to_sql)
  32. end
  33. 1 def self.user_tag_stream(user, tag_ids)
  34. 16 owned_or_visible_by_user(user).tag_stream(tag_ids)
  35. end
  36. 1 def self.public_tag_stream(tag_ids)
  37. 31 all_public.select("DISTINCT #{table_name}.*").tag_stream(tag_ids)
  38. end
  39. 1 def self.tag_stream(tag_ids)
  40. 47 joins(:taggings).where("taggings.tag_id IN (?)", tag_ids)
  41. end
  42. 1 def nsfw
  43. 204 !!(text.try(:match, /#nsfw/i) || super) # rubocop:disable Style/DoubleNegation
  44. end
  45. 1 def comment_email_subject
  46. 21 if message.present?
  47. 20 message.title
  48. 1 elsif photos.present?
  49. 1 I18n.t("posts.show.photos_by", count: photos.size, author: author_name)
  50. end
  51. end
  52. 1 def first_photo_url(*args)
  53. photos.first.url(*args)
  54. end
  55. 1 def text_and_photos_blank?
  56. 2382 text.blank? && photos.blank?
  57. end
  58. 1 def queue_gather_oembed_data
  59. 17 Workers::GatherOEmbedData.perform_async(self.id, self.oembed_url)
  60. end
  61. 1 def queue_gather_open_graph_data
  62. 39 Workers::GatherOpenGraphData.perform_async(self.id, self.open_graph_url)
  63. end
  64. 1 def contains_oembed_url_in_text?
  65. 4717 urls = self.message.urls
  66. 4835 self.oembed_url = urls.find{ |url| !TRUSTED_OEMBED_PROVIDERS.find(url).nil? }
  67. end
  68. 1 def contains_open_graph_url_in_text?
  69. 2359 return nil if self.contains_oembed_url_in_text?
  70. 2341 self.open_graph_url = self.message.urls[0]
  71. end
  72. 1 def post_location
  73. {
  74. 150 address: location.try(:address),
  75. lat: location.try(:lat),
  76. lng: location.try(:lng)
  77. }
  78. end
  79. 1 def receive(recipient_user_ids)
  80. 984 super(recipient_user_ids)
  81. 988 photos.each {|photo| photo.receive(recipient_user_ids) }
  82. end
  83. # Note: the next two methods can be safely removed once changes from #6818 are deployed on every pod
  84. # see StatusMessageCreationService#dispatch
  85. # Only includes those people, to whom we're going to send a federation entity
  86. # (and doesn't define exhaustive list of people who can receive it)
  87. 1 def people_allowed_to_be_mentioned
  88. 125 @aspects_ppl ||=
  89. 90 if public?
  90. 54 :all
  91. else
  92. 36 Contact.joins(:aspect_memberships).where(aspect_memberships: {aspect: aspects}).distinct.pluck(:person_id)
  93. end
  94. end
  95. 1 def filter_mentions
  96. 88 return if people_allowed_to_be_mentioned == :all
  97. 35 update(text: Diaspora::Mentionable.filter_people(text, people_allowed_to_be_mentioned))
  98. end
  99. 1 private
  100. 1 def presence_of_content
  101. 2379 errors.add(:base, "Cannot create a StatusMessage without content") if text_and_photos_blank?
  102. end
  103. end

app/models/tag_following.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class TagFollowing < ApplicationRecord
  3. 1 belongs_to :user
  4. 1 belongs_to :tag, :class_name => "ActsAsTaggableOn::Tag"
  5. 1 validates_uniqueness_of :tag_id, :scope => :user_id
  6. 1 def self.user_is_following?(user, tagname)
  7. 4 tagname.nil? ? false : joins(:tag).where(:tags => {:name => tagname.downcase}).where(:user_id => user.id).exists?
  8. end
  9. end

app/models/user.rb

0.0% lines covered

472 relevant lines. 0 lines covered and 472 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. require "attr_encrypted"
  6. class User < ApplicationRecord
  7. include Connecting
  8. include Querying
  9. include SocialActions
  10. apply_simple_captcha :message => I18n.t('simple_captcha.message.failed'), :add_to_base => true
  11. scope :logged_in_since, ->(time) { where('last_seen > ?', time) }
  12. scope :monthly_actives, ->(time = Time.now) { logged_in_since(time - 1.month) }
  13. scope :daily_actives, ->(time = Time.now) { logged_in_since(time - 1.day) }
  14. scope :yearly_actives, ->(time = Time.now) { logged_in_since(time - 1.year) }
  15. scope :halfyear_actives, ->(time = Time.now) { logged_in_since(time - 6.month) }
  16. scope :active, -> { joins(:person).where(people: {closed_account: false}) }
  17. attr_encrypted :otp_secret, if: false, prefix: "plain_"
  18. devise :two_factor_authenticatable,
  19. :two_factor_backupable,
  20. otp_backup_code_length: 16,
  21. otp_number_of_backup_codes: 10
  22. devise :registerable,
  23. :recoverable, :rememberable, :trackable, :validatable,
  24. :lockable, :lastseenable, :lock_strategy => :none, :unlock_strategy => :none
  25. before_validation :strip_and_downcase_username
  26. before_validation :set_current_language, :on => :create
  27. before_validation :set_default_color_theme, on: :create
  28. validates :username, presence: true, uniqueness: true, format: {with: /\A[A-Za-z0-9_.\-]+\z/},
  29. length: {maximum: 32}, exclusion: {in: AppConfig.settings.username_blacklist}
  30. validates_inclusion_of :language, :in => AVAILABLE_LANGUAGE_CODES
  31. validates :color_theme, inclusion: {in: AVAILABLE_COLOR_THEMES}, allow_blank: true
  32. validates_format_of :unconfirmed_email, :with => Devise.email_regexp, :allow_blank => true
  33. validate :unconfirmed_email_quasiuniqueness
  34. validates :person, presence: true
  35. validates_associated :person
  36. validate :no_person_with_same_username
  37. serialize :hidden_shareables, Hash
  38. serialize :otp_backup_codes, Array
  39. has_one :person, inverse_of: :owner, foreign_key: :owner_id
  40. has_one :profile, through: :person
  41. delegate :guid, :public_key, :posts, :photos, :owns?, :image_url,
  42. :diaspora_handle, :name, :atom_url, :profile_url, :profile, :url,
  43. :first_name, :last_name, :full_name, :gender, :participations, to: :person
  44. delegate :id, :guid, to: :person, prefix: true
  45. has_many :aspects, -> { order('order_id ASC') }
  46. belongs_to :auto_follow_back_aspect, class_name: "Aspect", optional: true
  47. belongs_to :invited_by, class_name: "User", optional: true
  48. has_many :invited_users, class_name: "User", inverse_of: :invited_by, foreign_key: :invited_by_id
  49. has_many :aspect_memberships, :through => :aspects
  50. has_many :contacts
  51. has_many :contact_people, :through => :contacts, :source => :person
  52. has_many :services
  53. has_many :user_preferences
  54. has_many :tag_followings
  55. has_many :followed_tags, -> { order('tags.name') }, :through => :tag_followings, :source => :tag
  56. has_many :blocks
  57. has_many :ignored_people, :through => :blocks, :source => :person
  58. has_many :conversation_visibilities, through: :person
  59. has_many :conversations, through: :conversation_visibilities
  60. has_many :notifications, :foreign_key => :recipient_id
  61. has_many :reports
  62. has_many :pairwise_pseudonymous_identifiers, class_name: "Api::OpenidConnect::PairwisePseudonymousIdentifier"
  63. has_many :authorizations, class_name: "Api::OpenidConnect::Authorization"
  64. has_many :o_auth_applications, through: :authorizations, class_name: "Api::OpenidConnect::OAuthApplication"
  65. has_many :share_visibilities
  66. before_save :guard_unconfirmed_email
  67. after_save :remove_invalid_unconfirmed_emails
  68. before_destroy do
  69. raise "Never destroy users!"
  70. end
  71. def self.all_sharing_with_person(person)
  72. User.joins(:contacts).where(:contacts => {:person_id => person.id})
  73. end
  74. def unread_notifications
  75. notifications.where(:unread => true)
  76. end
  77. def unread_message_count
  78. ConversationVisibility.where(person_id: self.person_id).sum(:unread)
  79. end
  80. def process_invite_acceptence(invite)
  81. self.invited_by = invite.user
  82. invite.use! unless AppConfig.settings.enable_registrations?
  83. end
  84. def invitation_code
  85. InvitationCode.find_or_create_by(user_id: self.id)
  86. end
  87. def hidden_shareables
  88. self[:hidden_shareables] ||= {}
  89. end
  90. def add_hidden_shareable(key, share_id, opts={})
  91. if self.hidden_shareables.has_key?(key)
  92. self.hidden_shareables[key] << share_id
  93. else
  94. self.hidden_shareables[key] = [share_id]
  95. end
  96. self.save unless opts[:batch]
  97. self.hidden_shareables
  98. end
  99. def remove_hidden_shareable(key, share_id)
  100. if self.hidden_shareables.has_key?(key)
  101. self.hidden_shareables[key].delete(share_id)
  102. end
  103. end
  104. def is_shareable_hidden?(shareable)
  105. shareable_type = shareable.class.base_class.name
  106. if self.hidden_shareables.has_key?(shareable_type)
  107. self.hidden_shareables[shareable_type].include?(shareable.id.to_s)
  108. else
  109. false
  110. end
  111. end
  112. def toggle_hidden_shareable(share)
  113. share_id = share.id.to_s
  114. key = share.class.base_class.to_s
  115. if self.hidden_shareables.has_key?(key) && self.hidden_shareables[key].include?(share_id)
  116. self.remove_hidden_shareable(key, share_id)
  117. self.save
  118. false
  119. else
  120. self.add_hidden_shareable(key, share_id)
  121. self.save
  122. true
  123. end
  124. end
  125. def has_hidden_shareables_of_type?(t = Post)
  126. share_type = t.base_class.to_s
  127. self.hidden_shareables[share_type].present?
  128. end
  129. # Copy the method provided by Devise to be able to call it later
  130. # from a Sidekiq job
  131. alias_method :send_reset_password_instructions!, :send_reset_password_instructions
  132. def send_reset_password_instructions
  133. Workers::ResetPassword.perform_async(self.id)
  134. end
  135. def update_user_preferences(pref_hash)
  136. if self.disable_mail
  137. UserPreference::VALID_EMAIL_TYPES.each{|x| self.user_preferences.find_or_create_by(email_type: x)}
  138. self.disable_mail = false
  139. self.save
  140. end
  141. pref_hash.keys.each do |key|
  142. if pref_hash[key] == 'true'
  143. self.user_preferences.find_or_create_by(email_type: key)
  144. else
  145. block = user_preferences.find_by(email_type: key)
  146. if block
  147. block.destroy
  148. end
  149. end
  150. end
  151. end
  152. def strip_and_downcase_username
  153. if username.present?
  154. username.strip!
  155. username.downcase!
  156. end
  157. end
  158. def disable_getting_started
  159. self.update_attribute(:getting_started, false) if self.getting_started?
  160. end
  161. def set_current_language
  162. self.language = I18n.locale.to_s if self.language.blank?
  163. end
  164. def set_default_color_theme
  165. self.color_theme ||= AppConfig.settings.default_color_theme
  166. end
  167. # This override allows a user to enter either their email address or their username into the username field.
  168. # @return [User] The user that matches the username/email condition.
  169. # @return [nil] if no user matches that condition.
  170. def self.find_for_database_authentication(conditions={})
  171. conditions = conditions.dup
  172. conditions[:username] = conditions[:username].downcase
  173. if conditions[:username] =~ /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i # email regex
  174. conditions[:email] = conditions.delete(:username)
  175. end
  176. where(conditions).first
  177. end
  178. def confirm_email(token)
  179. return false if token.blank? || token != confirm_email_token
  180. self.email = unconfirmed_email
  181. save
  182. end
  183. ######## Posting ########
  184. def build_post(class_name, opts={})
  185. opts[:author] = person
  186. model_class = class_name.to_s.camelize.constantize
  187. model_class.diaspora_initialize(opts)
  188. end
  189. def dispatch_post(post, opts={})
  190. logger.info "user:#{id} dispatching #{post.class}:#{post.guid}"
  191. Diaspora::Federation::Dispatcher.defer_dispatch(self, post, opts)
  192. end
  193. def update_post(post, post_hash={})
  194. if self.owns? post
  195. post.update(post_hash)
  196. self.dispatch_post(post)
  197. end
  198. end
  199. def add_to_streams(post, aspects_to_insert)
  200. aspects_to_insert.each do |aspect|
  201. aspect << post
  202. end
  203. end
  204. def aspects_from_ids(aspect_ids)
  205. if aspect_ids == "all" || aspect_ids == :all
  206. self.aspects
  207. else
  208. aspects.where(:id => aspect_ids).to_a
  209. end
  210. end
  211. def post_default_aspects
  212. if post_default_public
  213. ["public"]
  214. else
  215. aspects.where(post_default: true).to_a
  216. end
  217. end
  218. def update_post_default_aspects(post_default_aspect_ids)
  219. aspects.each do |aspect|
  220. enable = post_default_aspect_ids.include?(aspect.id.to_s)
  221. aspect.update_attribute(:post_default, enable)
  222. end
  223. end
  224. # Check whether the user has liked a post.
  225. # @param [Post] post
  226. def liked?(target)
  227. if target.likes.loaded?
  228. if self.like_for(target)
  229. return true
  230. else
  231. return false
  232. end
  233. else
  234. Like.exists?(:author_id => self.person.id, :target_type => target.class.base_class.to_s, :target_id => target.id)
  235. end
  236. end
  237. # Get the user's like of a post, if there is one.
  238. # @param [Post] post
  239. # @return [Like]
  240. def like_for(target)
  241. if target.likes.loaded?
  242. target.likes.find {|like| like.author_id == person.id }
  243. else
  244. Like.find_by(author_id: person.id, target_type: target.class.base_class.to_s, target_id: target.id)
  245. end
  246. end
  247. ######### Data export ##################
  248. mount_uploader :export, ExportedUser
  249. def queue_export
  250. update exporting: true, export: nil, exported_at: nil
  251. Workers::ExportUser.perform_async(id)
  252. end
  253. def perform_export!
  254. export = Tempfile.new([username, ".json.gz"], encoding: "ascii-8bit")
  255. export.write(compressed_export) && export.close
  256. if export.present?
  257. update exporting: false, export: export, exported_at: Time.zone.now
  258. else
  259. update exporting: false
  260. end
  261. rescue StandardError => e
  262. logger.error "Unexpected error while exporting data for '#{username}': #{e.class}: #{e.message}\n" \
  263. "#{e.backtrace.first(15).join("\n")}"
  264. update exporting: false
  265. end
  266. def compressed_export
  267. ActiveSupport::Gzip.compress Diaspora::Exporter.new(self).execute
  268. end
  269. ######### Photo export ##################
  270. mount_uploader :exported_photos_file, ExportedPhotos
  271. def queue_export_photos
  272. update exporting_photos: true, exported_photos_file: nil, exported_photos_at: nil
  273. Workers::ExportPhotos.perform_async(id)
  274. end
  275. def perform_export_photos!
  276. PhotoExporter.new(self).perform
  277. rescue StandardError => e
  278. logger.error "Unexpected error while exporting photos for '#{username}': #{e.class}: #{e.message}\n" \
  279. "#{e.backtrace.first(15).join("\n")}"
  280. update exporting_photos: false
  281. end
  282. ######### Mailer #######################
  283. def mail(job, *args)
  284. return unless job.present?
  285. pref = job.to_s.gsub('Workers::Mail::', '').underscore
  286. if(self.disable_mail == false && !self.user_preferences.exists?(:email_type => pref))
  287. job.perform_async(*args)
  288. end
  289. end
  290. def send_confirm_email
  291. return if unconfirmed_email.blank?
  292. Workers::Mail::ConfirmEmail.perform_async(id)
  293. end
  294. ######### Posts and Such ###############
  295. def retract(target)
  296. retraction = Retraction.for(target)
  297. retraction.defer_dispatch(self)
  298. retraction.perform
  299. end
  300. ########### Profile ######################
  301. def update_profile(params)
  302. if photo = params.delete(:photo)
  303. photo.update(pending: false) if photo.pending
  304. params[:image_url] = photo.url(:thumb_large)
  305. params[:image_url_medium] = photo.url(:thumb_medium)
  306. params[:image_url_small] = photo.url(:thumb_small)
  307. end
  308. params.stringify_keys!
  309. params.slice!(*(Profile.column_names+['tag_string', 'date']))
  310. if profile.update(params)
  311. deliver_profile_update
  312. true
  313. else
  314. false
  315. end
  316. end
  317. def update_profile_with_omniauth( user_info )
  318. update_profile( self.profile.from_omniauth_hash( user_info ) )
  319. end
  320. def deliver_profile_update(opts={})
  321. Diaspora::Federation::Dispatcher.defer_dispatch(self, profile, opts)
  322. end
  323. def basic_profile_present?
  324. tag_followings.any? || profile[:image_url]
  325. end
  326. ### Helpers ############
  327. def self.build(opts={})
  328. u = User.new(opts.except(:person, :id))
  329. u.setup(opts)
  330. u
  331. end
  332. def self.find_or_build(opts={})
  333. user = User.find_by(username: opts[:username])
  334. user ||= User.build(opts)
  335. user
  336. end
  337. def setup(opts)
  338. self.username = opts[:username]
  339. self.email = opts[:email]
  340. self.language = opts[:language]
  341. self.language ||= I18n.locale.to_s
  342. self.color_theme = opts[:color_theme]
  343. self.color_theme ||= AppConfig.settings.default_color_theme
  344. valid?
  345. errors = self.errors
  346. errors.delete :person
  347. return if errors.size > 0
  348. self.set_person(Person.new((opts[:person] || {}).except(:id)))
  349. self.generate_keys
  350. self
  351. end
  352. def set_person(person)
  353. person.diaspora_handle = "#{self.username}#{User.diaspora_id_host}"
  354. self.person = person
  355. end
  356. def self.diaspora_id_host
  357. "@#{AppConfig.bare_pod_uri}"
  358. end
  359. def seed_aspects
  360. self.aspects.create(:name => I18n.t('aspects.seed.family'))
  361. self.aspects.create(:name => I18n.t('aspects.seed.friends'))
  362. self.aspects.create(:name => I18n.t('aspects.seed.work'))
  363. aq = self.aspects.create(:name => I18n.t('aspects.seed.acquaintances'))
  364. if AppConfig.settings.autofollow_on_join?
  365. begin
  366. default_account = Person.find_or_fetch_by_identifier(AppConfig.settings.autofollow_on_join_user)
  367. share_with(default_account, aq)
  368. rescue DiasporaFederation::Discovery::DiscoveryError
  369. logger.warn "Error auto-sharing with #{AppConfig.settings.autofollow_on_join_user}
  370. fix autofollow_on_join_user in configuration."
  371. end
  372. end
  373. aq
  374. end
  375. def send_welcome_message
  376. return unless AppConfig.settings.welcome_message.enabled? && AppConfig.admins.account?
  377. sender_username = AppConfig.admins.account.get
  378. sender = User.find_by(username: sender_username)
  379. return if sender.nil?
  380. conversation = sender.build_conversation(
  381. participant_ids: [sender.person.id, person.id],
  382. subject: AppConfig.settings.welcome_message.subject.get,
  383. message: {text: AppConfig.settings.welcome_message.text.get % {username: username}}
  384. )
  385. Diaspora::Federation::Dispatcher.build(sender, conversation).dispatch if conversation.save
  386. end
  387. def encryption_key
  388. OpenSSL::PKey::RSA.new(serialized_private_key)
  389. end
  390. def admin?
  391. Role.is_admin?(self.person)
  392. end
  393. def moderator?
  394. Role.moderator?(person)
  395. end
  396. def moderator_only?
  397. Role.moderator_only?(person)
  398. end
  399. def spotlight?
  400. Role.spotlight?(person)
  401. end
  402. def podmin_account?
  403. username == AppConfig.admins.account
  404. end
  405. def mine?(target)
  406. if target.present? && target.respond_to?(:user_id)
  407. return self.id == target.user_id
  408. end
  409. false
  410. end
  411. # Ensure that the unconfirmed email isn't already someone's email
  412. def unconfirmed_email_quasiuniqueness
  413. if User.exists?(["id != ? AND email = ?", id, unconfirmed_email])
  414. errors.add(:unconfirmed_email, I18n.t("errors.messages.taken"))
  415. end
  416. end
  417. def guard_unconfirmed_email
  418. self.unconfirmed_email = nil if unconfirmed_email.blank? || unconfirmed_email == email
  419. return unless will_save_change_to_unconfirmed_email?
  420. self.confirm_email_token = unconfirmed_email ? SecureRandom.hex(15) : nil
  421. end
  422. # Whenever email is set, clear all unconfirmed emails which match
  423. def remove_invalid_unconfirmed_emails
  424. return unless saved_change_to_email?
  425. # rubocop:disable Rails/SkipsModelValidations
  426. User.where(unconfirmed_email: email).update_all(unconfirmed_email: nil, confirm_email_token: nil)
  427. # rubocop:enable Rails/SkipsModelValidations
  428. end
  429. # Generate public/private keys for User and associated Person
  430. def generate_keys
  431. key_size = (Rails.env == "test" ? 512 : 4096)
  432. self.serialized_private_key = OpenSSL::PKey::RSA.generate(key_size).to_s if serialized_private_key.blank?
  433. if self.person && self.person.serialized_public_key.blank?
  434. self.person.serialized_public_key = OpenSSL::PKey::RSA.new(self.serialized_private_key).public_key.to_s
  435. end
  436. end
  437. def no_person_with_same_username
  438. diaspora_id = "#{username}#{User.diaspora_id_host}"
  439. return unless username_changed? && Person.exists?(diaspora_handle: diaspora_id)
  440. errors.add(:base, "That username has already been taken")
  441. end
  442. def close_account!
  443. self.person.lock_access!
  444. self.lock_access!
  445. AccountDeletion.create(person: person)
  446. end
  447. def closed_account?
  448. self.person.closed_account
  449. end
  450. def clear_account!
  451. clearable_fields.each do |field|
  452. self[field] = nil
  453. end
  454. [:getting_started,
  455. :show_community_spotlight_in_stream,
  456. :post_default_public].each do |field|
  457. self[field] = false
  458. end
  459. self.remove_export = true
  460. self.remove_exported_photos_file = true
  461. self[:disable_mail] = true
  462. self[:email] = "deletedaccount_#{self[:id]}@example.org"
  463. random_password = SecureRandom.hex(20)
  464. self.password = random_password
  465. self.password_confirmation = random_password
  466. self.save(:validate => false)
  467. end
  468. def sign_up
  469. if AppConfig.settings.captcha.enable?
  470. save_with_captcha
  471. else
  472. save
  473. end
  474. end
  475. def flag_for_removal(remove_after)
  476. # flag inactive user for future removal
  477. if AppConfig.settings.maintenance.remove_old_users.enable?
  478. self.remove_after = remove_after
  479. self.save
  480. end
  481. end
  482. def after_database_authentication
  483. # remove any possible remove_after timestamp flag set by maintenance.remove_old_users
  484. unless self.remove_after.nil?
  485. self.remove_after = nil
  486. self.save
  487. end
  488. end
  489. def remember_me
  490. true
  491. end
  492. private
  493. def clearable_fields
  494. attributes.keys - %w(id username encrypted_password created_at updated_at locked_at
  495. serialized_private_key getting_started
  496. disable_mail show_community_spotlight_in_stream
  497. email remove_after export exporting
  498. exported_photos_file exporting_photos)
  499. end
  500. end

app/models/user/connecting.rb

0.0% lines covered

43 relevant lines. 0 lines covered and 43 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class User
  6. module Connecting
  7. # This will create a contact on the side of the sharer and the sharee.
  8. # @param [Person] person The person to start sharing with.
  9. # @param [Aspect] aspect The aspect to add them to.
  10. # @return [Contact] The newly made contact for the passed in person.
  11. def share_with(person, aspect)
  12. return if blocks.where(person_id: person.id).exists?
  13. contact = contacts.find_or_initialize_by(person_id: person.id)
  14. return false unless contact.valid?
  15. needs_dispatch = !contact.receiving?
  16. contact.receiving = true
  17. contact.aspects << aspect
  18. contact.save
  19. if needs_dispatch
  20. Diaspora::Federation::Dispatcher.defer_dispatch(self, contact)
  21. deliver_profile_update(subscriber_ids: [person.id]) unless person.local?
  22. end
  23. Notifications::StartedSharing.where(recipient_id: id, target: person.id, unread: true)
  24. .update_all(unread: false)
  25. contact
  26. end
  27. def disconnect(contact)
  28. logger.info "event=disconnect user=#{diaspora_handle} target=#{contact.person.diaspora_handle}"
  29. if contact.person.local?
  30. raise "FATAL: user entry is missing from the DB. Aborting" if contact.person.owner.nil?
  31. contact.person.owner.disconnected_by(contact.user.person)
  32. else
  33. ContactRetraction.for(contact).defer_dispatch(self)
  34. end
  35. contact.aspect_memberships.delete_all
  36. disconnect_contact(contact, direction: :receiving, destroy: !contact.sharing)
  37. end
  38. def disconnected_by(person)
  39. logger.info "event=disconnected_by user=#{diaspora_handle} target=#{person.diaspora_handle}"
  40. contact_for(person).try {|contact| disconnect_contact(contact, direction: :sharing, destroy: !contact.receiving) }
  41. end
  42. private
  43. def disconnect_contact(contact, direction:, destroy:)
  44. if destroy
  45. contact.destroy
  46. else
  47. contact.update(direction => false)
  48. end
  49. end
  50. end
  51. end

app/models/user/querying.rb

0.0% lines covered

132 relevant lines. 0 lines covered and 132 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #TODO: THIS FILE SHOULD NOT EXIST, EVIL SQL SHOULD BE ENCAPSULATED IN EvilQueries,
  6. #throwing all of this stuff in user violates demeter like WHOA
  7. module User::Querying
  8. def find_visible_shareable_by_id(klass, id, opts={} )
  9. key = (opts.delete(:key) || :id)
  10. ::EvilQuery::VisibleShareableById.new(self, klass, key, id, opts).post!
  11. end
  12. def visible_shareables(klass, opts={})
  13. opts = prep_opts(klass, opts)
  14. shareable_ids = visible_shareable_ids(klass, opts)
  15. klass.where(id: shareable_ids).select("DISTINCT #{klass.table_name}.*")
  16. .limit(opts[:limit]).order(opts[:order_with_table])
  17. end
  18. def visible_shareable_ids(klass, opts={})
  19. visible_ids_from_sql(klass, prep_opts(klass, opts))
  20. end
  21. def contact_for(person)
  22. return nil unless person
  23. contact_for_person_id(person.id)
  24. end
  25. def block_for(person)
  26. return nil unless person
  27. blocks.find_by(person_id: person.id)
  28. end
  29. def aspects_with_shareable(base_class_name_or_class, shareable_id)
  30. base_class_name = base_class_name_or_class
  31. base_class_name = base_class_name_or_class.base_class.to_s if base_class_name_or_class.is_a?(Class)
  32. self.aspects.joins(:aspect_visibilities).where(:aspect_visibilities => {:shareable_id => shareable_id, :shareable_type => base_class_name})
  33. end
  34. def contact_for_person_id(person_id)
  35. Contact.includes(person: :profile).find_by(user_id: id, person_id: person_id)
  36. end
  37. # @param [Person] person
  38. # @return [Boolean] whether person is a contact of this user
  39. def has_contact_for?(person)
  40. Contact.exists?(:user_id => self.id, :person_id => person.id)
  41. end
  42. def people_in_aspects(requested_aspects, opts={})
  43. allowed_aspects = self.aspects & requested_aspects
  44. aspect_ids = allowed_aspects.map(&:id)
  45. people = Person.in_aspects(aspect_ids)
  46. if opts[:type] == 'remote'
  47. people = people.where(:owner_id => nil)
  48. elsif opts[:type] == 'local'
  49. people = people.where('people.owner_id IS NOT NULL')
  50. end
  51. people
  52. end
  53. def aspects_with_person person
  54. contact_for(person).aspects
  55. end
  56. def posts_from(person, with_order=true)
  57. base_query = Post.from_person_visible_by_user(self, person)
  58. return base_query.order("posts.created_at desc") if with_order
  59. base_query
  60. end
  61. def photos_from(person, opts={})
  62. opts = prep_opts(Photo, opts)
  63. Photo.from_person_visible_by_user(self, person)
  64. .by_max_time(opts[:max_time])
  65. .limit(opts[:limit])
  66. end
  67. protected
  68. # @return [Array<Integer>]
  69. def visible_ids_from_sql(klass, opts)
  70. opts[:klass] = klass
  71. opts[:by_members_of] ||= aspect_ids
  72. klass.connection.select_values(visible_shareable_sql(opts)).map(&:to_i)
  73. end
  74. def visible_shareable_sql(opts)
  75. shareable_from_others = construct_shareable_from_others_query(opts)
  76. shareable_from_self = construct_shareable_from_self_query(opts)
  77. "(#{shareable_from_others.to_sql} LIMIT #{opts[:limit]}) " \
  78. "UNION ALL (#{shareable_from_self.to_sql} LIMIT #{opts[:limit]}) " \
  79. "ORDER BY #{opts[:order]} LIMIT #{opts[:limit]}"
  80. end
  81. def construct_shareable_from_others_query(opts)
  82. logger.debug "[EVIL-QUERY] user.construct_shareable_from_others_query"
  83. query = visible_shareables_query(posts_from_aspects_query(opts), opts)
  84. query = query.where(type: opts[:type]) unless opts[:klass] == Photo
  85. ugly_select_clause(query, opts)
  86. end
  87. # For PostgreSQL and MySQL/MariaDB we use a different query
  88. # see issue: https://github.com/diaspora/diaspora/issues/5014
  89. def posts_from_aspects_query(opts)
  90. if AppConfig.postgres?
  91. opts[:klass].where(author_id: Person.in_aspects(opts[:by_members_of]).select("people.id"))
  92. else
  93. person_ids = Person.connection.select_values(Person.in_aspects(opts[:by_members_of]).select("people.id").to_sql)
  94. opts[:klass].where(author_id: person_ids)
  95. end
  96. end
  97. def visible_shareables_query(query, opts)
  98. query.with_visibility.where(
  99. visible_private_shareables(opts).or(opts[:klass].arel_table[:public].eq(true))
  100. )
  101. end
  102. def visible_private_shareables(opts)
  103. ShareVisibility.arel_table[:user_id].eq(id)
  104. .and(ShareVisibility.arel_table[:shareable_type].eq(opts[:klass].to_s))
  105. .and(ShareVisibility.arel_table[:hidden].eq(opts[:hidden]))
  106. end
  107. def construct_shareable_from_self_query(opts)
  108. conditions = {author_id: person_id}
  109. conditions[:type] = opts[:type] if opts.has_key?(:type)
  110. query = opts[:klass].where(conditions)
  111. unless opts[:all_aspects?]
  112. query = query.with_aspects.where(
  113. AspectVisibility.arel_table[:aspect_id].in(opts[:by_members_of])
  114. .or(opts[:klass].arel_table[:public].eq(true))
  115. )
  116. end
  117. ugly_select_clause(query, opts)
  118. end
  119. def ugly_select_clause(query, opts)
  120. klass = opts[:klass]
  121. table = klass.table_name
  122. select_clause = "DISTINCT %s.id, %s.updated_at AS updated_at, %s.created_at AS created_at" % [table, table, table]
  123. query.select(select_clause).order(opts[:order_with_table])
  124. .where(klass.arel_table[opts[:order_field]].lt(opts[:max_time]))
  125. end
  126. # @return [Hash]
  127. def prep_opts(klass, opts)
  128. defaults = {
  129. :order => 'created_at DESC',
  130. :limit => 15,
  131. :hidden => false
  132. }
  133. defaults[:type] = Stream::Base::TYPES_OF_POST_IN_STREAM if klass == Post
  134. opts = defaults.merge(opts)
  135. if opts[:limit] == :all
  136. opts.delete(:limit)
  137. end
  138. opts[:order_field] = opts[:order].split.first.to_sym
  139. opts[:order_with_table] = klass.table_name + '.' + opts[:order]
  140. opts[:max_time] = Time.at(opts[:max_time]) if opts[:max_time].is_a?(Integer)
  141. opts[:max_time] ||= Time.now + 1
  142. opts
  143. end
  144. end

app/models/user/social_actions.rb

0.0% lines covered

58 relevant lines. 0 lines covered and 58 lines missed.
    
  1. # frozen_string_literal: true
  2. module User::SocialActions
  3. def comment!(target, text, opts={})
  4. Comment::Generator.new(self, target, text).create!(opts).tap do
  5. update_or_create_participation!(target)
  6. end
  7. end
  8. def participate!(target, opts={})
  9. Participation::Generator.new(self, target).create!(opts)
  10. end
  11. def like!(target, opts={})
  12. Like::Generator.new(self, target).create!(opts).tap do
  13. update_or_create_participation!(target)
  14. end
  15. end
  16. def like_comment!(target, opts={})
  17. Like::Generator.new(self, target).create!(opts).tap do
  18. update_or_create_participation!(target.commentable)
  19. end
  20. end
  21. def participate_in_poll!(target, answer, opts={})
  22. PollParticipation::Generator.new(self, target, answer).create!(opts).tap do
  23. update_or_create_participation!(target)
  24. end
  25. end
  26. def reshare!(target, opts={})
  27. raise I18n.t("reshares.create.error") if target.author.guid == guid
  28. build_post(:reshare, :root_guid => target.guid).tap do |reshare|
  29. reshare.save!
  30. update_or_create_participation!(target)
  31. Diaspora::Federation::Dispatcher.defer_dispatch(self, reshare)
  32. end
  33. end
  34. def build_conversation(opts={})
  35. Conversation.new do |c|
  36. c.author = self.person
  37. c.subject = opts[:subject]
  38. c.participant_ids = [*opts[:participant_ids]] | [self.person_id]
  39. c.messages_attributes = [
  40. { author: self.person, text: opts[:message][:text] }
  41. ]
  42. end
  43. end
  44. def build_message(conversation, opts={})
  45. conversation.messages.build(
  46. text: opts[:text],
  47. author: self.person
  48. )
  49. end
  50. def update_or_create_participation!(target)
  51. return if target.author == person
  52. participation = participations.find_by(target_id: target)
  53. if participation.present?
  54. participation.update!(count: participation.count.next)
  55. else
  56. participate!(target)
  57. end
  58. end
  59. end

app/models/user_preference.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class UserPreference < ApplicationRecord
  3. 1 belongs_to :user
  4. 1 validate :must_be_valid_email_type
  5. VALID_EMAIL_TYPES =
  6. 1 %w[
  7. someone_reported
  8. mentioned
  9. mentioned_in_comment
  10. comment_on_post
  11. private_message
  12. started_sharing
  13. also_commented
  14. liked
  15. liked_comment
  16. reshared
  17. contacts_birthday
  18. ].freeze
  19. 1 def must_be_valid_email_type
  20. 69 unless VALID_EMAIL_TYPES.include?(self.email_type)
  21. 1 errors.add(:email_type, 'supplied mail type is not a valid or known email type')
  22. end
  23. end
  24. end

app/presenters/aspect_membership_presenter.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class AspectMembershipPresenter < BasePresenter
  3. 1 def initialize(membership)
  4. 87 @membership = membership
  5. end
  6. 1 def base_hash
  7. {
  8. 87 id: @membership.id,
  9. aspect: AspectPresenter.new(@membership.aspect).as_json,
  10. }
  11. end
  12. end

app/presenters/aspect_presenter.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class AspectPresenter < BasePresenter
  3. 1 def initialize(aspect)
  4. 321 @aspect = aspect
  5. end
  6. 1 def as_json
  7. 310 { :id => @aspect.id,
  8. :name => @aspect.name,
  9. }
  10. end
  11. 1 def as_api_json(full=false, with_order: true)
  12. values = {
  13. 11 id: @aspect.id,
  14. name: @aspect.name
  15. }
  16. 11 values[:order] = @aspect.order_id if with_order
  17. 11 values
  18. end
  19. 1 def to_json(options={})
  20. 1 as_json.to_json(options)
  21. end
  22. end

app/presenters/avatar_presenter.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class AvatarPresenter < BasePresenter
  3. 1 def base_hash(with_default=false)
  4. avatar = {
  5. 115 small: small(with_default),
  6. medium: medium(with_default),
  7. large: large(with_default),
  8. raw: raw(with_default) # rubocop:disable Rails/OutputSafety
  9. }.compact
  10. 115 avatar unless avatar.empty?
  11. end
  12. 1 def small(with_default=false)
  13. 115 image_url(size: :thumb_small, fallback_to_default: with_default)
  14. end
  15. 1 def medium(with_default=false)
  16. 446 image_url(size: :thumb_medium, fallback_to_default: with_default)
  17. end
  18. 1 def large(with_default=false)
  19. 115 image_url(size: :scaled_full, fallback_to_default: with_default)
  20. end
  21. 1 def raw(with_default=false)
  22. # TODO: Replace me with the actual raw photo.
  23. 115 image_url(fallback_to_default: with_default)
  24. end
  25. end

app/presenters/base_presenter.rb

100.0% lines covered

21 relevant lines. 21 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class BasePresenter
  3. 1 attr_reader :current_user
  4. 1 include Rails.application.routes.url_helpers
  5. 1 class << self
  6. 1 def new(*args)
  7. 2002 return NilPresenter.new if args[0].nil?
  8. 1922 super *args
  9. end
  10. 1 def as_collection(collection, method=:as_json, *args)
  11. 749 collection.map{|object| self.new(object, *args).send(method) }
  12. end
  13. end
  14. 1 def initialize(presentable, curr_user=nil)
  15. 1509 @presentable = presentable
  16. 1509 @current_user = curr_user
  17. end
  18. 1 def respond_to_missing?(method, include_private=false)
  19. 71 @presentable.respond_to?(method) || super
  20. end
  21. 1 def method_missing(method, *args, **kwargs) # rubocop:disable Lint/MissingSuper
  22. 4817 @presentable.public_send(method, *args, **kwargs)
  23. end
  24. 1 class NilPresenter
  25. 1 def method_missing(method, *args)
  26. nil
  27. end
  28. end
  29. 1 private
  30. 1 def default_url_options
  31. 52 {host: AppConfig.pod_uri.host, port: AppConfig.pod_uri.port}
  32. end
  33. end

app/presenters/block_presenter.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class BlockPresenter < BasePresenter
  3. 1 def base_hash
  4. 1 { id: id }
  5. end
  6. end

app/presenters/comment_presenter.rb

94.44% lines covered

18 relevant lines. 17 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class CommentPresenter < BasePresenter
  3. 1 def as_json(_opts={})
  4. {
  5. 6 id: id,
  6. guid: guid,
  7. text: message.plain_text_for_json,
  8. author: author.as_api_response(:backbone),
  9. created_at: created_at,
  10. mentioned_people: mentioned_people.as_api_response(:backbone),
  11. interactions: build_interactions_json
  12. }
  13. end
  14. 1 def as_api_response
  15. {
  16. 8 guid: guid,
  17. body: message.plain_text_for_json,
  18. author: PersonPresenter.new(author).as_api_json,
  19. created_at: created_at,
  20. mentioned_people: build_mentioned_people_json,
  21. reported: current_user.present? && reports.exists?(user: current_user),
  22. interactions: build_interaction_state
  23. }
  24. end
  25. 1 def build_interaction_state
  26. {
  27. 8 liked: current_user.present? && likes.exists?(author: current_user.person),
  28. likes_count: likes_count
  29. }
  30. end
  31. 1 def build_interactions_json
  32. {
  33. 6 likes: as_api(own_likes(likes)),
  34. likes_count: likes_count
  35. }
  36. end
  37. 1 def build_mentioned_people_json
  38. 9 mentioned_people.map {|m| PersonPresenter.new(m).as_api_json }
  39. end
  40. # TODO: Only send the own_like boolean.
  41. # Frontend uses the same methods for post-likes as for comment-likes
  42. # Whenever the frontend will be refactored, just send the own_like boolean, instead of a full list of likes
  43. # The list of likes is already send when API requests the full list.
  44. 1 def own_likes(likes)
  45. 6 if current_user
  46. 5 likes.where(author: current_user.person)
  47. else
  48. 1 likes.none
  49. end
  50. end
  51. 1 def as_api(collection)
  52. 6 collection.includes(author: :profile).map {|element|
  53. element.as_api_response(:backbone)
  54. }
  55. end
  56. end

app/presenters/contact_presenter.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ContactPresenter < BasePresenter
  3. 1 def base_hash
  4. {
  5. 112 id: id,
  6. person_id: person_id
  7. }
  8. end
  9. 1 def full_hash
  10. 111 base_hash.merge(
  11. 76 aspect_memberships: aspect_memberships.map{ |membership| AspectMembershipPresenter.new(membership).base_hash }
  12. )
  13. end
  14. 1 def full_hash_with_person
  15. 43 full_hash.merge(person: person_without_contact)
  16. end
  17. 1 def as_api_json_without_contact
  18. 2 PersonPresenter.new(person, current_user).as_api_json
  19. end
  20. 1 private
  21. 1 def person_without_contact
  22. 43 PersonPresenter.new(person, current_user).as_json.except!(:contact)
  23. end
  24. end

app/presenters/conversation_presenter.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ConversationPresenter < BasePresenter
  3. 1 def as_api_json
  4. 56 read = @presentable.conversation_visibilities.find_by(person_id: current_user.person_id)&.unread == 0
  5. {
  6. 56 guid: @presentable.guid,
  7. subject: @presentable.subject,
  8. created_at: @presentable.created_at,
  9. read: read,
  10. 111 participants: @presentable.participants.map {|x| PersonPresenter.new(x).as_api_json }
  11. }
  12. end
  13. end

app/presenters/last_three_comments_decorator.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class LastThreeCommentsDecorator
  3. 1 def initialize(presenter)
  4. 32 @presenter = presenter
  5. end
  6. 1 def as_json(_options={})
  7. 32 current_user = @presenter.current_user
  8. 32 @presenter.as_json.tap do |post|
  9. 32 post[:interactions].merge!(comments: CommentPresenter.as_collection(
  10. @presenter.post.last_three_comments,
  11. :as_json,
  12. current_user
  13. ))
  14. end
  15. end
  16. end

app/presenters/likes_presenter.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class LikesPresenter < BasePresenter
  3. 1 def as_api_json
  4. {
  5. 8 guid: @presentable.guid,
  6. author: PersonPresenter.new(@presentable.author).as_api_json
  7. }
  8. end
  9. end

app/presenters/message_presenter.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class MessagePresenter < BasePresenter
  3. 1 def as_api_json
  4. {
  5. 4 guid: @presentable.guid,
  6. created_at: @presentable.created_at,
  7. body: @presentable.text,
  8. author: PersonPresenter.new(@presentable.author).as_api_json
  9. }
  10. end
  11. end

app/presenters/node_info_presenter.rb

100.0% lines covered

62 relevant lines. 62 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class NodeInfoPresenter
  3. 1 delegate :as_json, :content_type, to: :document
  4. 1 def initialize(version)
  5. 18 @version = version
  6. end
  7. 1 def document
  8. 22 @document ||= NodeInfo.build do |doc|
  9. 15 doc.version = @version
  10. 15 add_static_data doc
  11. 15 add_configuration doc
  12. 15 add_user_counts doc.usage.users
  13. 15 add_usage doc.usage
  14. end
  15. end
  16. 1 def add_configuration(doc)
  17. 15 doc.software.version = version
  18. 15 doc.services.outbound = available_services
  19. 15 doc.open_registrations = open_registrations?
  20. 15 doc.metadata["nodeName"] = name
  21. 15 doc.metadata["camo"] = camo_config
  22. 15 doc.metadata["adminAccount"] = admin_account
  23. end
  24. 1 def add_static_data(doc)
  25. 15 doc.software.name = "diaspora"
  26. 15 doc.software.repository = "https://github.com/diaspora/diaspora"
  27. 15 doc.software.homepage = "https://diasporafoundation.org/"
  28. 15 doc.protocols.protocols << "diaspora"
  29. end
  30. 1 def add_user_counts(doc)
  31. 15 return unless expose_user_counts?
  32. 1 doc.total = total_users
  33. 1 doc.active_halfyear = halfyear_users
  34. 1 doc.active_month = monthly_users
  35. end
  36. 1 def add_usage(doc)
  37. 15 doc.local_posts = local_posts if expose_posts_counts?
  38. 15 doc.local_comments = local_comments if expose_comment_counts?
  39. end
  40. 1 def expose_user_counts?
  41. 17 AppConfig.privacy.statistics.user_counts?
  42. end
  43. 1 def expose_posts_counts?
  44. 17 AppConfig.privacy.statistics.post_counts?
  45. end
  46. 1 def expose_comment_counts?
  47. 17 AppConfig.privacy.statistics.comment_counts?
  48. end
  49. 1 def name
  50. 17 AppConfig.settings.pod_name
  51. end
  52. 1 def version
  53. 17 AppConfig.version_string
  54. end
  55. 1 def open_registrations?
  56. 20 AppConfig.settings.enable_registrations?
  57. end
  58. 1 def camo_config
  59. {
  60. 15 markdown: AppConfig.privacy.camo.proxy_markdown_images?,
  61. opengraph: AppConfig.privacy.camo.proxy_opengraph_thumbnails?,
  62. remotePods: AppConfig.privacy.camo.proxy_remote_pod_images?
  63. }
  64. end
  65. 1 def admin_account
  66. 15 AppConfig.admins.account if AppConfig.admins.account?
  67. end
  68. 1 def available_services
  69. 27 Configuration::KNOWN_SERVICES.select {|service|
  70. 81 AppConfig.show_service?(service, nil)
  71. }.map(&:to_s)
  72. end
  73. 1 def total_users
  74. 1 @total_users ||= User.active.count
  75. end
  76. 1 def monthly_users
  77. 1 @monthly_users ||= User.monthly_actives.count
  78. end
  79. 1 def halfyear_users
  80. 1 @halfyear_users ||= User.halfyear_actives.count
  81. end
  82. 1 def local_posts
  83. 2 Rails.cache.fetch("NodeInfoPresenter/local_posts", expires_in: 1.hour) do
  84. 2 @local_posts ||= Post.where(type: "StatusMessage")
  85. .joins(:author)
  86. .where.not(people: {owner_id: nil})
  87. .count
  88. end
  89. end
  90. 1 def local_comments
  91. 2 Rails.cache.fetch("NodeInfoPresenter/local_comments", expires_in: 1.hour) do
  92. 2 @local_comments ||= Comment.joins(:author)
  93. .where.not(people: {owner_id: nil})
  94. .count
  95. end
  96. end
  97. end

app/presenters/notification_presenter.rb

100.0% lines covered

19 relevant lines. 19 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class NotificationPresenter < BasePresenter
  3. 1 def as_api_json
  4. 16 data = base_hash
  5. 16 data = data.merge(target: target_json) if target
  6. 16 data
  7. end
  8. 1 private
  9. 1 def base_hash
  10. {
  11. 16 guid: guid,
  12. type: type_as_json,
  13. read: !unread,
  14. created_at: created_at,
  15. event_creators: creators_json
  16. }
  17. end
  18. 1 def target_json
  19. 14 json = {guid: target.guid}
  20. 14 json[:author] = PersonPresenter.new(target.author).as_api_json if target.author
  21. 14 json
  22. end
  23. 1 def creators_json
  24. 32 actors.map {|actor| PersonPresenter.new(actor).as_api_json }
  25. end
  26. 1 def type_as_json
  27. 16 NotificationService::NOTIFICATIONS_REVERSE_JSON_TYPES[type]
  28. end
  29. 1 def target
  30. 58 return linked_object if linked_object&.is_a?(Post)
  31. 26 return linked_object.post if linked_object&.respond_to?(:post)
  32. end
  33. end

app/presenters/o_embed_presenter.rb

100.0% lines covered

19 relevant lines. 19 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class OEmbedPresenter
  3. 1 include PostsHelper
  4. 1 def initialize(post, opts = {})
  5. 7 @post = post
  6. 7 @opts = opts
  7. end
  8. 1 def to_json(opts={})
  9. 1 as_json(opts).to_json
  10. end
  11. 1 def as_json(opts={})
  12. {
  13. 3 :provider_name => "Diaspora",
  14. :provider_url => AppConfig.pod_uri.to_s,
  15. :type => 'rich',
  16. :version => '1.0',
  17. :title => post_title,
  18. :author_name => post_author,
  19. :author_url => post_author_url,
  20. :width => @opts.fetch(:maxwidth, 516),
  21. :height => @opts.fetch(:maxheight, 320),
  22. :html => iframe_html
  23. }
  24. end
  25. 1 def self.id_from_url(url)
  26. 4 URI.parse(url).path.gsub(%r{\/posts\/|\/p\/}, '')
  27. end
  28. 1 def post_title
  29. 3 post_page_title(@post)
  30. end
  31. 1 def post_author
  32. 3 @post.author_name
  33. end
  34. 1 def post_author_url
  35. 3 AppConfig.url_to(Rails.application.routes.url_helpers.person_path(@post.author))
  36. end
  37. 1 def iframe_html
  38. 4 post_iframe_url(@post.id, :height => @opts[:maxheight], :width => @opts[:maxwidth])
  39. end
  40. end

app/presenters/person_presenter.rb

97.4% lines covered

77 relevant lines. 75 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PersonPresenter < BasePresenter
  3. 1 def base_hash
  4. {
  5. 122 id: id,
  6. guid: guid,
  7. name: name,
  8. diaspora_id: diaspora_handle
  9. }
  10. end
  11. 1 def as_api_json
  12. {
  13. 321 guid: guid,
  14. diaspora_id: diaspora_handle,
  15. name: name,
  16. avatar: AvatarPresenter.new(@presentable).medium
  17. }.compact
  18. end
  19. 1 def full_hash
  20. 122 base_hash_with_contact.merge(
  21. relationship: relationship,
  22. 122 block: is_blocked? ? BlockPresenter.new(current_user_person_block).base_hash : false,
  23. is_own_profile: own_profile?,
  24. show_profile_info: public_details? || own_profile? || person_is_following_current_user
  25. )
  26. end
  27. 1 def profile_hash_as_api_json
  28. 13 if own_profile?
  29. 6 ProfilePresenter.new(profile).as_self_api_json.merge(guid: guid)
  30. else
  31. 7 show_detailed = @presentable.public_details? || person_is_following_current_user
  32. 7 ProfilePresenter.new(profile).as_other_api_json(show_detailed).merge(
  33. guid: guid,
  34. blocked: is_blocked?,
  35. relationship: relationship_detailed,
  36. aspects: aspects_detailed
  37. )
  38. end
  39. end
  40. 1 def as_json(_options={})
  41. 99 full_hash_with_profile
  42. end
  43. 1 def hovercard
  44. 9 full_hash.merge(profile: ProfilePresenter.new(profile).for_hovercard)
  45. end
  46. 1 def metas_attributes
  47. {
  48. 35 keywords: {name: "keywords", content: comma_separated_tags},
  49. description: {name: "description", content: description},
  50. og_title: {property: "og:title", content: title},
  51. og_description: {property: "og:title", content: description},
  52. og_url: {property: "og:url", content: url},
  53. og_image: {property: "og:image", content: image_url},
  54. og_type: {property: "og:type", content: "profile"},
  55. og_profile_username: {property: "og:profile:username", content: name},
  56. og_profile_firstname: {property: "og:profile:first_name", content: first_name},
  57. og_profile_lastname: {property: "og:profile:last_name", content: last_name}
  58. }
  59. end
  60. 1 def self.people_names(people)
  61. people.map(&:name).join(", ")
  62. end
  63. 1 protected
  64. 1 def own_profile?
  65. 375 current_user.try(:person) == @presentable
  66. end
  67. 1 def relationship
  68. 122 return false unless current_user
  69. 108 contact = current_user_person_contact
  70. 108 return :not_sharing unless contact
  71. 67 %i(mutual sharing receiving).find do |status|
  72. 113 contact.public_send("#{status}?")
  73. end || :not_sharing
  74. end
  75. 1 def relationship_detailed
  76. 7 status = {receiving: false, sharing: false}
  77. 7 return status unless current_user
  78. 7 contact = current_user_person_contact
  79. 7 return status unless contact
  80. 3 status.keys.each do |key|
  81. 6 status[key] = contact.public_send("#{key}?")
  82. end
  83. 3 status
  84. end
  85. 1 def aspects_detailed
  86. 7 return [] unless current_user_person_contact
  87. 3 aspects_for_person = current_user.aspects_with_person(@presentable)
  88. 4 aspects_for_person.map {|a| AspectPresenter.new(a).as_api_json(false, with_order: false) }
  89. end
  90. 1 def person_is_following_current_user
  91. 115 return false unless current_user
  92. 102 contact = current_user_person_contact
  93. 102 contact && contact.sharing?
  94. end
  95. 1 def base_hash_with_contact
  96. 122 base_hash.merge(
  97. 122 contact: (!own_profile? && has_contact?) ? contact_hash : false
  98. )
  99. end
  100. 1 def full_hash_with_profile
  101. 99 attrs = full_hash
  102. 99 if attrs[:show_profile_info]
  103. 47 attrs.merge!(profile: ProfilePresenter.new(profile).private_hash)
  104. else
  105. 52 attrs.merge!(profile: ProfilePresenter.new(profile).public_hash)
  106. end
  107. 99 attrs
  108. end
  109. 1 def contact_hash
  110. 67 ContactPresenter.new(current_user_person_contact).full_hash
  111. end
  112. 1 private
  113. 1 def current_user_person_block
  114. 130 @block ||= (current_user ? current_user.block_for(@presentable) : Block.none)
  115. end
  116. 1 def current_user_person_contact
  117. 406 @contact ||= (current_user ? current_user.contact_for(@presentable) : Contact.none)
  118. end
  119. 1 def has_contact?
  120. 115 current_user_person_contact.present?
  121. end
  122. 1 def is_blocked?
  123. 129 current_user_person_block.present?
  124. end
  125. 1 def title
  126. 36 name
  127. end
  128. 1 def comma_separated_tags
  129. 36 profile.tags.map(&:name).join(", ") if profile.tags
  130. end
  131. 1 def url
  132. 36 url_for(@presentable)
  133. end
  134. 1 def description
  135. 70 public_details? ? bio : ""
  136. end
  137. 1 def image_url
  138. 36 return AppConfig.url_to @presentable.image_url if @presentable.image_url[0] == "/"
  139. @presentable.image_url
  140. end
  141. end

app/presenters/photo_presenter.rb

88.89% lines covered

9 relevant lines. 8 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PhotoPresenter < BasePresenter
  3. 1 def base_hash
  4. {
  5. id: id,
  6. guid: guid,
  7. dimensions: {
  8. height: height,
  9. width: width
  10. },
  11. sizes: {
  12. small: url(:thumb_small),
  13. medium: url(:thumb_medium),
  14. large: url(:scaled_full),
  15. raw: url
  16. }
  17. }
  18. end
  19. 1 def as_api_json(full=false)
  20. api_json = {
  21. 33 dimensions: {
  22. height: height,
  23. width: width
  24. },
  25. sizes: {
  26. small: url(:thumb_small),
  27. medium: url(:thumb_medium),
  28. large: url(:scaled_full),
  29. raw: url
  30. }
  31. }
  32. 33 api_json[:guid] = guid if full
  33. 33 api_json[:created_at] = created_at if full
  34. 33 api_json[:post] = status_message_guid if full && status_message_guid
  35. 33 api_json
  36. end
  37. end

app/presenters/pod_presenter.rb

100.0% lines covered

4 relevant lines. 4 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PodPresenter < BasePresenter
  3. 1 def base_hash(*_arg)
  4. {
  5. 12 id: id,
  6. host: host,
  7. port: port,
  8. ssl: ssl,
  9. status: status,
  10. checked_at: checked_at,
  11. response_time: response_time,
  12. active: active?,
  13. offline: offline?,
  14. offline_since: offline_since,
  15. created_at: created_at,
  16. software: software,
  17. error: error
  18. }
  19. end
  20. 1 alias_method :as_json, :base_hash
  21. end

app/presenters/poll_presenter.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PollPresenter < BasePresenter
  3. 1 def initialize(poll, current_user=nil)
  4. 7 super(poll, current_user)
  5. 7 @participation = participation_answer(current_user) if current_user
  6. end
  7. 1 def as_api_json
  8. {
  9. 11 guid: guid,
  10. participation_count: participation_count,
  11. question: question,
  12. already_participated: @participation.present?,
  13. poll_answers: answers_collection_as_api_json(poll_answers)
  14. }
  15. end
  16. 1 private
  17. 1 def answers_collection_as_api_json(answers_collection)
  18. 35 answers_collection.map {|answer| answer_as_api_json(answer) }
  19. end
  20. 1 def answer_as_api_json(answer)
  21. base = {
  22. 24 id: answer.id,
  23. answer: answer.answer,
  24. vote_count: answer.vote_count
  25. }
  26. 24 base[:own_answer] = @participation.try(:poll_answer_id) == answer.id if current_user
  27. 24 base
  28. end
  29. end

app/presenters/post_interaction_presenter.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PostInteractionPresenter
  3. 1 def initialize(post, current_user)
  4. 90 @post = post
  5. 90 @current_user = current_user
  6. end
  7. 1 def as_json(_options={})
  8. {
  9. 12 likes: as_api(@post.likes),
  10. reshares: PostPresenter.as_collection(@post.reshares, :as_json, @current_user),
  11. comments: CommentPresenter.as_collection(@post.comments.order("created_at ASC")),
  12. participations: as_api(participations),
  13. comments_count: @post.comments_count,
  14. likes_count: @post.likes_count,
  15. reshares_count: @post.reshares_count
  16. }
  17. end
  18. 1 def as_counters
  19. {
  20. 78 comments: @post.comments_count,
  21. likes: @post.likes_count,
  22. reshares: @post.reshares_count
  23. }
  24. end
  25. 1 private
  26. 1 def participations
  27. 12 return @post.participations.none unless @current_user
  28. 8 @post.participations.where(author: @current_user.person)
  29. end
  30. 1 def as_api(collection)
  31. 24 collection.includes(author: :profile).map {|element|
  32. 7 element.as_api_response(:backbone)
  33. }
  34. end
  35. end

app/presenters/post_presenter.rb

97.7% lines covered

87 relevant lines. 85 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PostPresenter < BasePresenter
  3. 1 include PostsHelper
  4. 1 include MetaDataHelper
  5. 1 attr_accessor :post
  6. 1 def initialize(presentable, current_user=nil)
  7. 188 @post = presentable
  8. 188 super
  9. end
  10. 1 def as_json(_options={})
  11. 86 @post.as_json(only: directly_retrieved_attributes)
  12. .merge(non_directly_retrieved_attributes)
  13. end
  14. 1 def as_api_response # rubocop:disable Metrics/AbcSize
  15. {
  16. 78 guid: @post.guid,
  17. body: build_text,
  18. title: title,
  19. post_type: @post.post_type,
  20. public: @post.public,
  21. created_at: @post.created_at,
  22. nsfw: @post.nsfw,
  23. author: PersonPresenter.new(@post.author).as_api_json,
  24. provider_display_name: @post.provider_display_name,
  25. interaction_counters: PostInteractionPresenter.new(@post, current_user).as_counters,
  26. location: location_as_api_json,
  27. poll: PollPresenter.new(@post.poll, current_user).as_api_json,
  28. mentioned_people: build_mentioned_people_json,
  29. photos: build_photos_json,
  30. root: root_api_response,
  31. own_interaction_state: build_own_interaction_state,
  32. open_graph_object: open_graph_object_api_response,
  33. oembed: @post.o_embed_cache.try(:data)
  34. }.compact
  35. end
  36. 1 def with_interactions
  37. 7 interactions = PostInteractionPresenter.new(@post, current_user)
  38. 7 as_json.merge!(interactions: interactions.as_json)
  39. end
  40. 1 def with_initial_interactions
  41. 8 as_json.tap do |post|
  42. 8 post[:interactions].merge!(
  43. likes: LikeService.new(current_user).find_for_post(@post.id).limit(30).as_api_response(:backbone),
  44. reshares: ReshareService.new(current_user).find_for_post(@post.id).limit(30).as_api_response(:backbone)
  45. )
  46. end
  47. end
  48. 1 def metas_attributes
  49. {
  50. 6 keywords: {name: "keywords", content: comma_separated_tags},
  51. description: {name: "description", content: description},
  52. og_url: {property: "og:url", content: url},
  53. og_title: {property: "og:title", content: title},
  54. og_image: {property: "og:image", content: images},
  55. og_description: {property: "og:description", content: description},
  56. og_article_tag: {property: "og:article:tag", content: tags},
  57. og_article_author: {property: "og:article:author", content: author_name},
  58. og_article_modified: {property: "og:article:modified_time", content: modified_time_iso8601},
  59. og_article_published: {property: "og:article:published_time", content: published_time_iso8601}
  60. }
  61. end
  62. 1 def page_title
  63. 6 post_page_title @post
  64. end
  65. 1 private
  66. 1 def directly_retrieved_attributes
  67. 86 %i(id guid public created_at interacted_at provider_display_name)
  68. end
  69. 1 def non_directly_retrieved_attributes
  70. {
  71. 86 text: build_text,
  72. post_type: @post.post_type,
  73. nsfw: @post.nsfw,
  74. author: @post.author.as_api_response(:backbone),
  75. o_embed_cache: @post.o_embed_cache.try(:as_api_response, :backbone),
  76. open_graph_cache: build_open_graph_cache,
  77. mentioned_people: build_mentioned_people_json,
  78. photos: build_photos_json,
  79. root: root,
  80. title: title,
  81. location: @post.post_location,
  82. poll: @post.poll,
  83. poll_participation_answer_id: poll_participation_answer_id,
  84. participation: participates?,
  85. interactions: build_interactions_json
  86. }
  87. end
  88. 1 def title
  89. 173 @post.message.present? ? @post.message.title : I18n.t("posts.presenter.title", name: @post.author_name)
  90. end
  91. 1 def build_text
  92. 164 if @post.message
  93. 164 @post.message.plain_text_for_json
  94. else
  95. @post.text
  96. end
  97. end
  98. 1 def build_open_graph_cache
  99. 88 @post.open_graph_cache.try(:as_api_response, :backbone)
  100. end
  101. 1 def open_graph_object_api_response
  102. 78 cache = @post.open_graph_cache
  103. 78 return unless cache
  104. {
  105. 2 type: cache.ob_type,
  106. url: cache.url,
  107. title: cache.title,
  108. image: cache.image,
  109. description: cache.description,
  110. video_url: cache.video_url
  111. }
  112. end
  113. 1 def build_mentioned_people_json
  114. 222 @post.mentioned_people.map {|m| PersonPresenter.new(m).as_api_json }
  115. end
  116. 1 def build_photos_json
  117. 176 @post.photos.map {|p| PhotoPresenter.new(p).as_api_json }
  118. end
  119. 1 def root
  120. 88 if @post.respond_to?(:absolute_root) && @post.absolute_root.present?
  121. 17 PostPresenter.new(@post.absolute_root, current_user).as_json
  122. end
  123. end
  124. 1 def root_api_response
  125. 78 is_root_post_exist = @post.respond_to?(:absolute_root) && @post.absolute_root.present?
  126. 78 PostPresenter.new(@post.absolute_root, current_user).as_api_response if is_root_post_exist
  127. end
  128. 1 def build_interactions_json
  129. {
  130. 86 likes: [user_like].compact,
  131. reshares: [user_reshare].compact,
  132. comments_count: @post.comments_count,
  133. likes_count: @post.likes_count,
  134. reshares_count: @post.reshares_count
  135. }
  136. end
  137. 1 def build_own_interaction_state
  138. 78 if current_user
  139. {
  140. 68 liked: @post.likes.where(author: current_user.person).exists?,
  141. reshared: @post.reshares.where(author: current_user.person).exists?,
  142. subscribed: participates?,
  143. reported: @post.reports.where(user: current_user).exists?
  144. }
  145. else
  146. 10 {
  147. liked: false,
  148. reshared: false,
  149. subscribed: false,
  150. reported: false
  151. }
  152. end
  153. end
  154. 1 def user_like
  155. 88 @post.like_for(current_user).try(:as_api_response, :backbone)
  156. end
  157. 1 def user_reshare
  158. 88 @post.reshare_for(current_user).try(:as_api_response, :backbone)
  159. end
  160. 1 def poll_participation_answer_id
  161. 86 @post.poll&.participation_answer(current_user)&.poll_answer_id if user_signed_in?
  162. end
  163. 1 def participates?
  164. 154 user_signed_in? && current_user.participations.where(target_id: @post).exists?
  165. end
  166. 1 def user_signed_in?
  167. 240 current_user.present?
  168. end
  169. 1 def person
  170. current_user.person
  171. end
  172. 1 def images
  173. 6 photos.any? ? photos.map(&:url) : default_image_url
  174. end
  175. 1 def published_time_iso8601
  176. 7 created_at.to_time.iso8601
  177. end
  178. 1 def modified_time_iso8601
  179. 7 updated_at.to_time.iso8601
  180. end
  181. 1 def tags
  182. 16 tags = @post.is_a?(Reshare) ? @post.absolute_root.try(:tags) : @post.tags
  183. 16 tags ? tags.map(&:name) : []
  184. end
  185. 1 def comma_separated_tags
  186. 7 tags.join(", ")
  187. end
  188. 1 def url
  189. 7 post_url @post
  190. end
  191. 1 def description
  192. 16 message.try(:plain_text_without_markdown, truncate: 1000)
  193. end
  194. 1 def location_as_api_json
  195. 78 location = @post.post_location
  196. 78 return if location.values.all?(&:nil?)
  197. 2 location[:lat] = location[:lat].to_f
  198. 2 location[:lng] = location[:lng].to_f
  199. 2 location
  200. end
  201. end

app/presenters/profile_presenter.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ProfilePresenter < BasePresenter
  3. 1 include PeopleHelper
  4. 1 def base_hash
  5. {
  6. 99 id: id,
  7. searchable: searchable
  8. }
  9. end
  10. 1 def public_hash
  11. 99 base_hash.merge(
  12. avatar: AvatarPresenter.new(@presentable).base_hash(true),
  13. tags: tags.pluck(:name)
  14. )
  15. end
  16. 1 def for_hovercard
  17. {
  18. 10 avatar: AvatarPresenter.new(@presentable).medium,
  19. tags: tags.pluck(:name)
  20. }
  21. end
  22. 1 def as_self_api_json
  23. 6 base_api_json.merge(added_details_api_json).merge(
  24. searchable: searchable,
  25. show_profile_info: public_details,
  26. nsfw: nsfw
  27. )
  28. end
  29. 1 def as_other_api_json(all_details)
  30. 7 return base_api_json unless all_details
  31. 5 base_api_json.merge(added_details_api_json)
  32. end
  33. 1 def private_hash
  34. 47 public_hash.merge(
  35. bio: bio_message.plain_text_for_json,
  36. birthday: formatted_birthday,
  37. gender: gender,
  38. location: location_message.plain_text_for_json
  39. )
  40. end
  41. 1 def formatted_birthday
  42. 47 birthday_format(birthday) if birthday
  43. end
  44. 1 private
  45. 1 def base_api_json
  46. {
  47. 13 name: [first_name, last_name].compact.join(" ").presence,
  48. diaspora_id: diaspora_handle,
  49. avatar: AvatarPresenter.new(@presentable).base_hash,
  50. tags: tags.pluck(:name)
  51. }.compact
  52. end
  53. 1 def added_details_api_json
  54. {
  55. 11 birthday: birthday.try(:iso8601),
  56. gender: gender,
  57. location: location_message.plain_text_for_json,
  58. bio: bio_message.plain_text_for_json
  59. }
  60. end
  61. end

app/presenters/service_presenter.rb

85.71% lines covered

7 relevant lines. 6 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ServicePresenter < BasePresenter
  3. 1 def initialize(service)
  4. 5 @service = service
  5. end
  6. 1 def as_json
  7. {
  8. 5 :provider => @service.provider
  9. }
  10. end
  11. 1 def to_json(options = {})
  12. as_json.to_json(options)
  13. end
  14. end

app/presenters/tag_stream_presenter.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class TagStreamPresenter < BasePresenter
  3. 1 def title
  4. 9 @presentable.display_tag_name
  5. end
  6. 1 def metas_attributes
  7. {
  8. 9 keywords: {name: "keywords", content: tag_name},
  9. description: {name: "description", content: description},
  10. og_url: {property: "og:url", content: url},
  11. og_title: {property: "og:title", content: title},
  12. og_description: {property: "og:description", content: description}
  13. }
  14. end
  15. 1 private
  16. 1 def description
  17. 18 I18n.t("streams.tags.title", tags: tag_name)
  18. end
  19. 1 def url
  20. 9 tag_url tag_name
  21. end
  22. end

app/presenters/user_application_presenter.rb

92.31% lines covered

26 relevant lines. 24 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class UserApplicationPresenter
  3. 1 attr_reader :scopes
  4. 1 def initialize(application, scopes, authorization_id=nil)
  5. 24 @app = application
  6. 24 @scopes = scopes
  7. 24 @authorization_id = authorization_id
  8. end
  9. 1 def id
  10. 1 @authorization_id
  11. end
  12. 1 def name
  13. 24 @app.client_name
  14. end
  15. 1 def image
  16. 24 @app.image_uri
  17. end
  18. 1 def terms_of_services
  19. @app.tos_uri
  20. end
  21. 1 def policy
  22. @app.policy_uri
  23. end
  24. 1 def name?
  25. 24 @app.client_name.present?
  26. end
  27. 1 def terms_of_services?
  28. 48 @app.tos_uri.present?
  29. end
  30. 1 def policy?
  31. 72 @app.policy_uri.present?
  32. end
  33. 1 def url
  34. 48 client_redirect = URI(@app.redirect_uris[0])
  35. 48 client_redirect.path = "/"
  36. 48 client_redirect.to_s
  37. end
  38. end

app/presenters/user_applications_presenter.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class UserApplicationsPresenter
  3. 1 def initialize(user)
  4. 1 @user = user
  5. end
  6. 1 def user_applications
  7. 2 @applications ||= @user.o_auth_applications.map do |app|
  8. 1 authorization = Api::OpenidConnect::Authorization.find_by_client_id_and_user(app.client_id, @user)
  9. 1 UserApplicationPresenter.new app, authorization.scopes, authorization.id
  10. end
  11. end
  12. 1 def applications_count
  13. 1 user_applications.size
  14. end
  15. 1 def applications?
  16. 1 applications_count > 0
  17. end
  18. end

app/presenters/user_presenter.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class UserPresenter
  3. 1 attr_accessor :user, :aspects_ids
  4. 1 def initialize(user, aspects_ids)
  5. 625 self.user = user
  6. 625 self.aspects_ids = aspects_ids
  7. end
  8. 1 def to_json(options={})
  9. 219 user.person.as_api_response(:backbone).update(
  10. notifications_count: notifications_count,
  11. unread_messages_count: unread_messages_count,
  12. admin: admin,
  13. moderator: moderator,
  14. aspects: aspects,
  15. services: services,
  16. following_count: user.contacts.receiving.count,
  17. configured_services: configured_services
  18. ).to_json(options)
  19. end
  20. 1 def services
  21. 220 ServicePresenter.as_collection(user.services)
  22. end
  23. 1 def configured_services
  24. 220 user.services.map(&:provider)
  25. end
  26. 1 def aspects
  27. 229 @aspects ||= begin
  28. 220 aspects = AspectPresenter.as_collection(user.aspects)
  29. 220 no_aspects = aspects_ids.empty?
  30. 441 aspects.each {|a| a[:selected] = no_aspects || aspects_ids.include?(a[:id].to_s) }
  31. end
  32. end
  33. 1 def notifications_count
  34. 219 @notification_count ||= user.unread_notifications.count
  35. end
  36. 1 def unread_messages_count
  37. 219 @unread_message_count ||= user.unread_message_count
  38. end
  39. 1 def admin
  40. 219 user.admin?
  41. end
  42. 1 def moderator
  43. 219 user.moderator?
  44. end
  45. end

app/serializers/export/aspect_serializer.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Export
  3. 1 class AspectSerializer < ActiveModel::Serializer
  4. 1 attributes :name
  5. end
  6. end

app/serializers/export/contact_serializer.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Export
  3. 1 class ContactSerializer < ActiveModel::Serializer
  4. 1 attributes :sharing,
  5. :receiving,
  6. :following,
  7. :followed,
  8. :person_guid,
  9. :person_name,
  10. :account_id,
  11. :public_key
  12. 1 has_many :contact_groups_membership
  13. 1 def following
  14. 19 object.sharing
  15. end
  16. 1 def followed
  17. 19 object.receiving
  18. end
  19. 1 def account_id
  20. 19 object.person_diaspora_handle
  21. end
  22. 1 def contact_groups_membership
  23. 19 object.aspects.map(&:name)
  24. end
  25. 1 def public_key
  26. 19 object.person.serialized_public_key
  27. end
  28. end
  29. end

app/serializers/export/others_data_serializer.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Export
  3. 1 class OthersDataSerializer < ActiveModel::Serializer
  4. # Relayables of other people in the archive: comments, likes, participations, poll participations where author is
  5. # the archive owner
  6. 1 has_many :relayables, serializer: FlatMapArraySerializer, each_serializer: FederationEntitySerializer
  7. 1 def initialize(user_id)
  8. 29 @user_id = user_id
  9. 29 super(object)
  10. end
  11. 1 private
  12. 1 def object
  13. 57 User.find(@user_id)
  14. end
  15. 1 def relayables
  16. 28 %i[comments likes poll_participations].map {|relayable|
  17. 84 others_relayables.send(relayable).find_each(batch_size: 20)
  18. }
  19. end
  20. 1 def others_relayables
  21. 84 @others_relayables ||= Diaspora::Exporter::OthersRelayables.new(object.person_id)
  22. end
  23. # Avoid calling pointless #embedded_in_root_associations method
  24. 1 def serializable_data
  25. 26 {}
  26. end
  27. end
  28. end

app/serializers/export/own_post_serializer.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Export
  3. # This is a serializer for the user's own posts
  4. 1 class OwnPostSerializer < FederationEntitySerializer
  5. # Only for public posts.
  6. # Includes URIs of pods which must be notified on the post updates.
  7. # Must always include local pod URI since we will want all the updates on the post if user migrates.
  8. 1 has_many :subscribed_pods_uris
  9. # Only for private posts.
  10. # Includes diaspora* IDs of people who must be notified on post updates.
  11. 1 has_many :subscribed_users_ids
  12. # Normally accepts Post as an object.
  13. 1 def initialize(*)
  14. 25 super
  15. 25 self.except = [excluded_subscription_key]
  16. end
  17. 1 private
  18. 1 def subscribed_pods_uris
  19. 8 object.subscribed_pods_uris.push(AppConfig.pod_uri.to_s)
  20. end
  21. 1 def subscribed_users_ids
  22. 17 object.subscribers.map(&:diaspora_handle)
  23. end
  24. 1 def excluded_subscription_key
  25. 25 object.public? ? :subscribed_users_ids : :subscribed_pods_uris
  26. end
  27. end
  28. end

app/serializers/export/own_relayables_serializer.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Export
  3. # This is a serializer for the user's own relayables. We remove signature from the own relayables since it isn't
  4. # useful and takes space.
  5. 1 class OwnRelayablesSerializer < FederationEntitySerializer
  6. 1 private
  7. 1 def modify_serializable_object(hash)
  8. 10 super.tap {|hash|
  9. 10 hash[:entity_data].delete(:author_signature)
  10. }
  11. end
  12. end
  13. end

app/serializers/export/user_serializer.rb

100.0% lines covered

38 relevant lines. 38 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Export
  3. 1 class UserSerializer < ActiveModel::Serializer
  4. 1 attributes :username,
  5. :email,
  6. :language,
  7. :private_key,
  8. :disable_mail,
  9. :show_community_spotlight_in_stream,
  10. :auto_follow_back,
  11. :auto_follow_back_aspect,
  12. :blocks
  13. 1 has_one :profile, serializer: FederationEntitySerializer
  14. 1 has_many :contact_groups, each_serializer: Export::AspectSerializer
  15. 1 has_many :contacts, each_serializer: Export::ContactSerializer
  16. 1 has_many :posts, each_serializer: Export::OwnPostSerializer
  17. 1 has_many :followed_tags
  18. 1 has_many :post_subscriptions
  19. 1 has_many :relayables, serializer: FlatMapArraySerializer, each_serializer: Export::OwnRelayablesSerializer
  20. 1 def initialize(user_id, options={})
  21. 35 @user_id = user_id
  22. 35 super(object, options)
  23. end
  24. 1 private
  25. 1 def object
  26. 575 User.find(@user_id)
  27. end
  28. 1 def posts
  29. 33 object.posts.find_each(batch_size: 20)
  30. end
  31. 1 def contacts
  32. 33 object.contacts.find_each(batch_size: 100)
  33. end
  34. 1 def relayables
  35. 33 [comments, likes, poll_participations].map {|relayable|
  36. 99 relayable.find_each(batch_size: 20)
  37. }
  38. end
  39. 1 def blocks
  40. 27 object.blocks.map(&:person_diaspora_handle)
  41. end
  42. 1 %i[comments likes poll_participations].each {|collection|
  43. 3 delegate collection, to: :person
  44. }
  45. 1 delegate :person, to: :object
  46. 1 def contact_groups
  47. 33 object.aspects
  48. end
  49. 1 def private_key
  50. 27 object.serialized_private_key
  51. end
  52. 1 def followed_tags
  53. 33 object.followed_tags.map(&:name)
  54. end
  55. 1 def post_subscriptions
  56. 33 Post.subscribed_by(object).pluck(:guid)
  57. end
  58. # Avoid calling pointless #embedded_in_root_associations method
  59. 1 def serializable_data
  60. 26 {}
  61. end
  62. end
  63. end

app/serializers/federation_entity_serializer.rb

92.86% lines covered

14 relevant lines. 13 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # This is an ActiveModel::Serializer based class which uses DiasporaFederation::Entity JSON serialization
  3. # features in order to serialize local DB objects. To determine a type of entity class to use the same routines
  4. # are used as for federation messages generation.
  5. 1 class FederationEntitySerializer < ActiveModel::Serializer
  6. 1 include SerializerPostProcessing
  7. 1 include Diaspora::Logging
  8. 1 private
  9. 1 def modify_serializable_object(hash)
  10. 81 hash.merge(entity.to_json)
  11. rescue DiasporaFederation::Entities::Relayable::AuthorPrivateKeyNotFound => e
  12. # The author of this relayable probably migrated from this pod to a different pod,
  13. # and we neither have the signature nor the new private key to generate a valid signature.
  14. # But we can use the private key of the old user to generate the signature it had when this entity was created
  15. 1 old_person = AccountMigration.joins(:old_person)
  16. .where("new_person_id = ? AND people.owner_id IS NOT NULL", object.author_id)
  17. .first.old_person
  18. 1 if old_person
  19. 1 logger.info "Using private key of #{old_person.diaspora_handle} to export: #{e.message}"
  20. 1 object.author = old_person
  21. 1 hash.merge(entity.to_json)
  22. else
  23. logger.warn "Skip entity for export because #{e.class}: #{e.message}"
  24. end
  25. end
  26. 1 def entity
  27. 82 Diaspora::Federation::Entities.build(object)
  28. end
  29. end

app/serializers/flat_map_array_serializer.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class FlatMapArraySerializer < ActiveModel::ArraySerializer
  3. 1 def serializable_object(options={})
  4. 61 @object.flat_map do |subarray|
  5. 183 subarray.map do |item|
  6. 21 serializer_for(item).serializable_object_with_notification(options)
  7. end
  8. end
  9. end
  10. end

app/serializers/notification_serializer.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class NotificationSerializer < ActiveModel::Serializer
  3. 1 attributes :id,
  4. :target_type,
  5. :target_id,
  6. :recipient_id,
  7. :unread,
  8. :created_at,
  9. :updated_at,
  10. :note_html
  11. 1 def note_html
  12. 7 context.render_to_string(partial: "notifications/notification", locals: {note: object, no_aspect_dropdown: true})
  13. end
  14. 1 def initialize(*_)
  15. 7 super
  16. 7 self.polymorphic = true
  17. 7 self.root = false
  18. end
  19. end

app/serializers/serializer_post_processing.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # This module encapsulates knowledge about the way AMS works with the serializable object.
  3. # The main responsibility of this module is to allow changing resulting object just before the
  4. # JSON serialization happens.
  5. 1 module SerializerPostProcessing
  6. # serializable_object output is used in AMS to produce a hash from input object that is passed to JSON serializer.
  7. # serializable_object of ActiveModel::Serializer is not documented as officialy available API
  8. # NOTE: if we ever move to AMS 0.10, this method was renamed there to serializable_hash
  9. 1 def serializable_object(options={})
  10. 83 modify_serializable_object(super)
  11. end
  12. # Users of this module may override this method in order to change serializable_object after
  13. # the serializable hash generation and before its serialization.
  14. 1 def modify_serializable_object(hash)
  15. 1 hash
  16. end
  17. # except is an array of keys that are excluded from serialized_object before JSON serialization
  18. 1 attr_accessor :except
  19. end

app/serializers/user_info_serializer.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class UserInfoSerializer < ActiveModel::Serializer
  3. 1 attributes :sub, :name, :nickname, :profile, :picture
  4. 1 def sub
  5. 4 auth = serialization_options[:authorization]
  6. 4 Api::OpenidConnect::SubjectIdentifierCreator.create(auth)
  7. end
  8. 1 def name
  9. 2 (object.first_name || "") + (object.last_name || "")
  10. end
  11. 1 def nickname
  12. 2 object.name
  13. end
  14. 1 def profile
  15. 2 api_v1_user_url
  16. end
  17. 1 def picture
  18. 2 object.image_url(fallback_to_default: false)
  19. end
  20. end

app/services/aspects_membership_service.rb

100.0% lines covered

35 relevant lines. 35 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class AspectsMembershipService
  3. 1 def initialize(user=nil)
  4. 71 @user = user
  5. end
  6. 1 def create(aspect_id, person_id)
  7. 47 person = Person.find(person_id)
  8. 46 aspect = @user.aspects.where(id: aspect_id).first
  9. 46 raise ActiveRecord::RecordNotFound unless person.present? && aspect.present?
  10. 42 contact = @user.share_with(person, aspect)
  11. 39 raise I18n.t("aspects.add_to_aspect.failure") if contact.blank?
  12. 38 AspectMembership.where(contact_id: contact.id, aspect_id: aspect.id).first
  13. end
  14. 1 def destroy_by_ids(aspect_id, contact_id)
  15. 9 aspect = @user.aspects.where(id: aspect_id).first
  16. 9 contact = @user.contacts.where(person_id: contact_id).first
  17. 9 destroy(aspect, contact)
  18. end
  19. 1 def destroy_by_membership_id(membership_id)
  20. 5 aspect = @user.aspects.joins(:aspect_memberships).where(aspect_memberships: {id: membership_id}).first
  21. 5 contact = @user.contacts.joins(:aspect_memberships).where(aspect_memberships: {id: membership_id}).first
  22. 5 destroy(aspect, contact)
  23. end
  24. 1 def contacts_in_aspect(aspect_id)
  25. 7 order = [Arel.sql("contact_id IS NOT NULL DESC"), "profiles.first_name ASC", "profiles.last_name ASC",
  26. "profiles.diaspora_handle ASC"]
  27. 7 @user.aspects.find(aspect_id) # to provide better error code if aspect isn't correct
  28. 4 contacts = @user.contacts.arel_table
  29. 4 aspect_memberships = AspectMembership.arel_table
  30. 4 @user.contacts.joins(
  31. contacts.join(aspect_memberships).on(
  32. aspect_memberships[:aspect_id].eq(aspect_id).and(
  33. aspect_memberships[:contact_id].eq(contacts[:id])
  34. )
  35. ).join_sources
  36. ).includes(person: :profile).order(order)
  37. end
  38. 1 def all_contacts
  39. 3 order = ["profiles.first_name ASC", "profiles.last_name ASC",
  40. "profiles.diaspora_handle ASC"]
  41. 3 @user.contacts.includes(person: :profile).order(order)
  42. end
  43. 1 private
  44. 1 def destroy(aspect, contact)
  45. 14 raise ActiveRecord::RecordNotFound unless aspect.present? && contact.present?
  46. 5 raise Diaspora::NotMine unless @user.mine?(aspect) && @user.mine?(contact)
  47. 5 membership = contact.aspect_memberships.where(aspect_id: aspect.id).first
  48. 5 raise ActiveRecord::RecordNotFound if membership.blank?
  49. 4 success = membership.destroy
  50. 4 {success: success, membership: membership}
  51. end
  52. end

app/services/block_service.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class BlockService
  3. 1 def initialize(user)
  4. 17 @user = user
  5. end
  6. 1 def block(person)
  7. 8 raise ActiveRecord::RecordNotUnique if @user.blocks.exists?(person: person)
  8. 7 block = @user.blocks.create!(person: person)
  9. 7 contact = @user.contact_for(person)
  10. 7 if contact
  11. 2 @user.disconnect(contact)
  12. 5 elsif block.person.remote?
  13. 1 Diaspora::Federation::Dispatcher.defer_dispatch(@user, block)
  14. end
  15. end
  16. 1 def unblock(person)
  17. 2 remove_block(@user.blocks.find_by!(person: person))
  18. end
  19. 1 def remove_block(block)
  20. 7 block.destroy
  21. 7 ContactRetraction.for(block).defer_dispatch(@user)
  22. end
  23. end

app/services/comment_service.rb

96.77% lines covered

31 relevant lines. 30 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class CommentService
  3. 1 def initialize(user=nil)
  4. 373 @user = user
  5. end
  6. 1 def create(post_id, text)
  7. 273 post = post_service.find!(post_id)
  8. 270 user.comment!(post, text)
  9. end
  10. 1 def find_for_post(post_id)
  11. 32 post_service.find!(post_id).comments.for_a_stream
  12. end
  13. 1 def find!(id_or_guid)
  14. 71 Comment.find_by!(comment_key(id_or_guid) => id_or_guid)
  15. end
  16. 1 def destroy(comment_id)
  17. 9 comment = Comment.find(comment_id)
  18. 7 if user.owns?(comment) || user.owns?(comment.parent)
  19. 5 user.retract(comment)
  20. 5 true
  21. else
  22. 2 false
  23. end
  24. end
  25. 1 def destroy!(comment_guid)
  26. 6 comment = find!(comment_guid)
  27. 5 if user.owns?(comment)
  28. 2 user.retract(comment)
  29. 3 elsif user.owns?(comment.parent)
  30. 1 user.retract(comment)
  31. 2 elsif comment
  32. 2 raise ActiveRecord::RecordInvalid
  33. else
  34. raise ActiveRecord::RecordNotFound
  35. end
  36. end
  37. 1 private
  38. 1 attr_reader :user
  39. # We can assume a guid is at least 16 characters long as we have guids set to hex(8) since we started using them.
  40. 1 def comment_key(id_or_guid)
  41. 71 id_or_guid.to_s.length < 16 ? :id : :guid
  42. end
  43. 1 def post_service
  44. 305 @post_service ||= PostService.new(user)
  45. end
  46. end

app/services/conversation_service.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ConversationService
  3. 1 def initialize(user=nil)
  4. 110 @user = user
  5. end
  6. 1 def all_for_user(filter={})
  7. 8 conversation_filter = {}
  8. 8 unless filter[:only_after].nil?
  9. conversation_filter = \
  10. 2 "conversations.created_at >= ?", filter[:only_after]
  11. end
  12. 8 visibility_filter = if filter[:unread]
  13. {
  14. 2 person_id: @user.person_id,
  15. unread: 1
  16. }
  17. else
  18. 6 {person_id: @user.person_id}
  19. end
  20. 8 Conversation.where(conversation_filter)
  21. .joins(:conversation_visibilities)
  22. .where(conversation_visibilities: visibility_filter)
  23. .all
  24. end
  25. 1 def build(subject, text, recipients)
  26. 46 person_ids = @user.contacts
  27. .mutual
  28. .where(person_id: recipients)
  29. .pluck(:person_id)
  30. opts = {
  31. 46 subject: subject,
  32. message: {text: text},
  33. participant_ids: person_ids
  34. }
  35. 46 @user.build_conversation(opts)
  36. end
  37. 1 def find!(conversation_guid)
  38. 56 conversation = Conversation.find_by!(guid: conversation_guid)
  39. 47 @user.conversations
  40. .joins(:conversation_visibilities)
  41. .where(conversation_visibilities: {
  42. person_id: @user.person_id,
  43. conversation_id: conversation.id
  44. }).first!
  45. end
  46. 1 def destroy!(conversation_guid)
  47. 3 conversation = find!(conversation_guid)
  48. 1 conversation.destroy!
  49. end
  50. 1 def get_visibility(conversation_guid)
  51. 6 conversation = find!(conversation_guid)
  52. 4 ConversationVisibility.where(
  53. person_id: @user.person.id,
  54. conversation_id: conversation.id
  55. ).first!
  56. end
  57. end

app/services/diaspora_link_service.rb

100.0% lines covered

29 relevant lines. 29 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Encapsulates logic of processing diaspora:// links
  3. 1 class DiasporaLinkService
  4. 1 attr_reader :type, :author, :guid
  5. 1 def initialize(link)
  6. 13 @link = link.dup
  7. 13 parse
  8. end
  9. 1 def find_or_fetch_entity
  10. 13 if type && guid
  11. 9 entity_finder.find || fetch_entity
  12. 4 elsif author
  13. 2 find_or_fetch_person
  14. end
  15. end
  16. 1 private
  17. 1 attr_accessor :link
  18. 1 def fetch_entity
  19. 4 DiasporaFederation::Federation::Fetcher.fetch_public(author, type, guid)
  20. 1 entity_finder.find
  21. rescue DiasporaFederation::Federation::Fetcher::NotFetchable
  22. 3 nil
  23. end
  24. 1 def entity_finder
  25. 10 @entity_finder ||= Diaspora::EntityFinder.new(type, guid)
  26. end
  27. 1 def find_or_fetch_person
  28. 2 Person.find_or_fetch_by_identifier(author)
  29. rescue DiasporaFederation::Discovery::DiscoveryError
  30. 1 nil
  31. end
  32. 1 def normalize
  33. 13 link.gsub!(%r{^web\+diaspora://}, "diaspora://") ||
  34. link.gsub!(%r{^//}, "diaspora://") ||
  35. %r{^diaspora://}.match(link) ||
  36. self.link = "diaspora://#{link}"
  37. end
  38. 1 def parse
  39. 13 normalize
  40. 13 match = DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX.match(link)
  41. 13 if match
  42. 9 @author, @type, @guid = match.captures
  43. else
  44. 4 @author = %r{^diaspora://(#{Validation::Rule::DiasporaId::DIASPORA_ID_REGEX})$}u.match(link)&.captures&.first
  45. end
  46. end
  47. end

app/services/import_service.rb

79.1% lines covered

67 relevant lines. 53 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ImportService
  3. 1 include Diaspora::Logging
  4. 1 def import_by_user(user, opts={})
  5. import_by_files(user.export.current_path, user.exported_photos_file.current_path, user.username, opts)
  6. end
  7. 1 def import_by_files(path_to_profile, path_to_photos, username, opts={})
  8. 2 if path_to_profile.present?
  9. logger.info "Import for profile #{username} at path #{path_to_profile} requested"
  10. import_user_profile(path_to_profile, username, opts.merge(photo_migration: path_to_photos.present?))
  11. end
  12. 2 user = User.find_by(username: username)
  13. 2 raise ArgumentError, "Username #{username} should exist before uploading photos." if user.nil?
  14. 2 if path_to_photos.present?
  15. 2 logger.info("Importing photos from import file for '#{username}' from #{path_to_photos}")
  16. 2 import_user_photos(user, path_to_photos)
  17. end
  18. 2 remove_file_references(user)
  19. end
  20. 1 private
  21. 1 def import_user_profile(path_to_profile, username, opts)
  22. raise ArgumentError, "Profile file not found at path: #{path_to_profile}" unless File.exist?(path_to_profile)
  23. service = MigrationService.new(path_to_profile, username, opts)
  24. logger.info "Start validating user profile #{username}"
  25. service.validate
  26. logger.info "Start importing user profile for '#{username}'"
  27. service.perform!
  28. logger.info "Successfully imported profile: #{username}"
  29. rescue MigrationService::ArchiveValidationFailed => e
  30. logger.error "Errors in the archive found: #{e.message}"
  31. rescue MigrationService::MigrationAlreadyExists
  32. logger.error "Migration record already exists for the user, can't continue"
  33. rescue MigrationService::SelfMigrationNotAllowed
  34. logger.error "You can't migrate onto your own account"
  35. end
  36. 1 def import_user_photos(user, path_to_photos)
  37. 2 raise ArgumentError, "Photos file not found at path: #{path_to_photos}" unless File.exist?(path_to_photos)
  38. 2 uncompressed_photos_folder = unzip_photos_file(path_to_photos)
  39. 2 user.posts.find_in_batches do |posts|
  40. 2 import_photos_for_posts(user, posts, uncompressed_photos_folder)
  41. end
  42. 2 FileUtils.rm_r(uncompressed_photos_folder)
  43. end
  44. 1 def import_photos_for_posts(user, posts, source_dir)
  45. 2 posts.each do |post|
  46. 2 post.photos.each do |photo|
  47. 2 uploaded_file = "#{source_dir}/#{photo.remote_photo_name}"
  48. 2 next unless File.exist?(uploaded_file) && photo.remote_photo_name.present?
  49. # Don't overwrite existing photos if they have the same filename.
  50. # Generate a new random filename if a conflict exists and re-federate the photo to update on remote pods.
  51. 2 random_string = File.basename(uploaded_file, ".*")
  52. 2 conflicting_photo_exists = Photo.where.not(id: photo.id).exists?(random_string: random_string)
  53. 2 random_string = SecureRandom.hex(10) if conflicting_photo_exists
  54. 2 store_and_process_photo(photo, uploaded_file, random_string)
  55. 2 Diaspora::Federation::Dispatcher.build(user, photo).dispatch if conflicting_photo_exists
  56. end
  57. end
  58. end
  59. 1 def store_and_process_photo(photo, uploaded_file, random_string)
  60. 2 File.open(uploaded_file) do |file|
  61. 2 photo.random_string = random_string
  62. 2 photo.keep_original_format = true
  63. 2 photo.unprocessed_image.store! file
  64. 2 photo.update_remote_path
  65. 2 photo.save(touch: false)
  66. end
  67. 2 photo.queue_processing_job
  68. end
  69. 1 def unzip_photos_file(photo_file_path)
  70. 2 folder = create_folder(photo_file_path)
  71. 2 Zip::File.open(photo_file_path) do |zip_file|
  72. 2 zip_file.each do |file|
  73. 2 target_name = "#{folder}#{Pathname::SEPARATOR_LIST}#{file}"
  74. 2 zip_file.extract(file, target_name) unless File.exist?(target_name)
  75. rescue Errno::ENOENT => e
  76. logger.error e.to_s
  77. end
  78. end
  79. 2 folder
  80. end
  81. 1 def create_folder(compressed_file_name)
  82. 2 extension = File.extname(compressed_file_name)
  83. 2 folder = compressed_file_name.delete_suffix(extension)
  84. 2 FileUtils.mkdir(folder) unless File.exist?(folder)
  85. 2 folder
  86. end
  87. 1 def remove_file_references(user)
  88. 2 user.remove_exported_photos_file
  89. 2 user.remove_export
  90. 2 user.save
  91. end
  92. end

app/services/like_service.rb

100.0% lines covered

44 relevant lines. 44 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class LikeService
  3. 1 def initialize(user=nil)
  4. 145 @user = user
  5. end
  6. 1 def create_for_post(post_id)
  7. 53 post = post_service.find!(post_id)
  8. 49 user.like!(post)
  9. end
  10. 1 def create_for_comment(comment_id)
  11. 31 comment = comment_service.find!(comment_id)
  12. 30 post_service.find!(comment.commentable_id) # checks implicit for visible posts
  13. 29 user.like_comment!(comment)
  14. end
  15. 1 def destroy(like_id)
  16. 9 like = Like.find(like_id)
  17. 7 if user.owns?(like)
  18. 3 user.retract(like)
  19. 3 true
  20. else
  21. 4 false
  22. end
  23. end
  24. 1 def find_for_post(post_id)
  25. 30 likes = post_service.find!(post_id).likes
  26. 26 user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes
  27. end
  28. 1 def find_for_comment(comment_id)
  29. 14 comment = comment_service.find!(comment_id)
  30. 14 post_service.find!(comment.post.id) # checks implicit for visible posts
  31. 12 likes = comment.likes
  32. 12 user ? likes.order(Arel.sql("author_id = #{user.person.id} DESC")) : likes
  33. end
  34. 1 def unlike_post(post_id)
  35. 4 likes = post_service.find!(post_id).likes
  36. 4 likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC"))
  37. 4 if !likes.empty? && user.owns?(likes[0])
  38. 3 user.retract(likes[0])
  39. 3 true
  40. else
  41. 1 false
  42. end
  43. end
  44. 1 def unlike_comment(comment_id)
  45. 4 likes = comment_service.find!(comment_id).likes
  46. 4 likes = likes.order(Arel.sql("author_id = #{user.person.id} DESC"))
  47. 4 if !likes.empty? && user.owns?(likes[0])
  48. 3 user.retract(likes[0])
  49. 3 true
  50. else
  51. 1 false
  52. end
  53. end
  54. 1 private
  55. 1 attr_reader :user
  56. 1 def post_service
  57. 131 @post_service ||= PostService.new(user)
  58. end
  59. 1 def comment_service
  60. 49 @comment_service ||= CommentService.new(user)
  61. end
  62. end

app/services/migration_service.rb

97.53% lines covered

81 relevant lines. 79 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class MigrationService
  3. 1 attr_reader :archive_path, :new_user_name, :opts
  4. 1 delegate :errors, :warnings, to: :archive_validator
  5. 1 def initialize(archive_path, new_user_name, opts={})
  6. 10 @archive_path = archive_path
  7. 10 @new_user_name = new_user_name
  8. 10 @opts = opts
  9. end
  10. 1 def validate
  11. 3 return unless archive_file_exists?
  12. 3 archive_validator.validate
  13. 3 raise ArchiveValidationFailed, errors.join("\n") if errors.any?
  14. 3 raise MigrationAlreadyExists if AccountMigration.where(old_person: old_person).any?
  15. 2 raise SelfMigrationNotAllowed if self_import?
  16. end
  17. 1 def perform!
  18. 2 find_or_create_user
  19. 2 import_archive
  20. 2 run_migration
  21. ensure
  22. 2 remove_intermediate_file
  23. end
  24. 1 def self_import?
  25. 2 source_diaspora_id = archive_validator.archive_author_diaspora_id
  26. 2 target_diaspora_id = "#{new_user_name}#{User.diaspora_id_host}"
  27. 2 source_diaspora_id == target_diaspora_id
  28. end
  29. # when old person can't be resolved we still import data but we don't create&perform AccountMigration instance
  30. 1 def only_import?
  31. 2 old_person.nil?
  32. end
  33. 1 private
  34. 1 def find_or_create_user
  35. 5 archive_importer.find_or_create_user(username: new_user_name, password: SecureRandom.hex)
  36. end
  37. 1 def import_archive
  38. 2 archive_importer.import(opts)
  39. end
  40. 1 def run_migration
  41. 2 account_migration.save
  42. 2 account_migration.perform!
  43. end
  44. 1 def account_migration
  45. 7 @account_migration ||= AccountMigration.new(
  46. old_person: old_person,
  47. new_person: archive_importer.user.person,
  48. old_private_key: archive_importer.serialized_private_key,
  49. old_person_diaspora_id: archive_importer.archive_author_diaspora_id,
  50. archive_contacts: archive_importer.contacts,
  51. remote_photo_path: remote_photo_path
  52. )
  53. end
  54. 1 def old_person
  55. 10 @old_person ||= Person.by_account_identifier(archive_validator.archive_author_diaspora_id)
  56. end
  57. 1 def archive_importer
  58. 27 @archive_importer ||= ArchiveImporter.new(archive_validator.archive_hash)
  59. end
  60. 1 def archive_validator
  61. 24 @archive_validator ||= ArchiveValidator.new(archive_file)
  62. end
  63. 1 def archive_file
  64. 10 return uncompressed_zip if zip_file?
  65. 9 return uncompressed_gz if gzip_file?
  66. 8 File.new(archive_path, "r")
  67. end
  68. 1 def archive_file_exists?
  69. 3 File.exist?(archive_path)
  70. end
  71. 1 def zip_file?
  72. 10 filetype = MIME::Types.type_for(archive_path).first.content_type
  73. 10 filetype.eql?("application/zip")
  74. end
  75. 1 def gzip_file?
  76. 9 filetype = MIME::Types.type_for(archive_path).first.content_type
  77. 9 filetype.eql?("application/gzip")
  78. end
  79. 1 def uncompressed_gz
  80. 1 target_dir = File.dirname(archive_path) + Pathname::SEPARATOR_LIST
  81. 1 unzipped_archive_file = "#{File.join(target_dir, SecureRandom.hex)}.json" # never override an existing file
  82. 1 Zlib::GzipReader.open(archive_path) {|gz|
  83. 1 File.open(unzipped_archive_file, "w") do |output_stream|
  84. 1 IO.copy_stream(gz, output_stream)
  85. end
  86. 1 @intermediate_file = unzipped_archive_file
  87. }
  88. 1 File.new(unzipped_archive_file, "r")
  89. end
  90. 1 def uncompressed_zip
  91. 1 target_dir = File.dirname(archive_path) + Pathname::SEPARATOR_LIST
  92. 1 zip_stream = Zip::InputStream.open(archive_path)
  93. 1 while entry = zip_stream.get_next_entry # rubocop:disable Lint/AssignmentInCondition
  94. 1 next unless entry.name.end_with?(".json")
  95. 1 target_file = "#{File.join(target_dir, SecureRandom.hex)}.json" # never override an existing file
  96. 1 entry.extract(target_file)
  97. 1 @intermediate_file = target_file
  98. 1 return File.new(target_file, "r")
  99. end
  100. end
  101. 1 def remove_intermediate_file
  102. # If an unzip operation created an unzipped file, remove it after migration
  103. 2 return if @intermediate_file.nil?
  104. return unless File.exist?(@intermediate_file)
  105. File.delete(@intermediate_file)
  106. end
  107. 1 def remote_photo_path
  108. 5 return unless opts.fetch(:photo_migration, false)
  109. 2 if AppConfig.environment.s3.enable?
  110. 1 return "https://#{AppConfig.environment.s3.bucket.get}.s3.amazonaws.com/uploads/images/"
  111. end
  112. 1 "#{AppConfig.pod_uri}uploads/images/"
  113. end
  114. 1 class ArchiveValidationFailed < RuntimeError
  115. end
  116. 1 class MigrationAlreadyExists < RuntimeError
  117. end
  118. 1 class SelfMigrationNotAllowed < RuntimeError
  119. end
  120. end

app/services/notification_service.rb

100.0% lines covered

26 relevant lines. 26 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class NotificationService
  3. NOTIFICATION_TYPES = {
  4. 1 Comment => [Notifications::MentionedInComment, Notifications::CommentOnPost, Notifications::AlsoCommented],
  5. Like => [Notifications::Liked, Notifications::LikedComment],
  6. StatusMessage => [Notifications::MentionedInPost],
  7. Conversation => [Notifications::PrivateMessage],
  8. Message => [Notifications::PrivateMessage],
  9. Reshare => [Notifications::Reshared],
  10. Contact => [Notifications::StartedSharing]
  11. }.freeze
  12. 1 NOTIFICATIONS_JSON_TYPES = {
  13. "also_commented" => "Notifications::AlsoCommented",
  14. "comment_on_post" => "Notifications::CommentOnPost",
  15. "liked" => "Notifications::Liked",
  16. "liked_comment" => "Notifications::LikedComment",
  17. "mentioned" => "Notifications::MentionedInPost",
  18. "mentioned_in_comment" => "Notifications::MentionedInComment",
  19. "reshared" => "Notifications::Reshared",
  20. "started_sharing" => "Notifications::StartedSharing",
  21. "contacts_birthday" => "Notifications::ContactsBirthday"
  22. }.freeze
  23. 1 NOTIFICATIONS_REVERSE_JSON_TYPES = NOTIFICATIONS_JSON_TYPES.invert.freeze
  24. 1 def initialize(user=nil)
  25. 2064 @user = user
  26. end
  27. 1 def index(unread_only=nil, only_after=nil)
  28. 12 query_string = "recipient_id = ? "
  29. 12 query_string += "AND unread = true " if unread_only
  30. 12 where_clause = [query_string, @user.id]
  31. 12 if only_after
  32. 5 query_string += " AND created_at >= ?"
  33. 5 where_clause = [query_string, @user.id, only_after]
  34. end
  35. 12 Notification.where(where_clause).includes(:target, actors: :profile)
  36. end
  37. 1 def get_by_guid(guid)
  38. 9 Notification.where(recipient_id: @user.id, guid: guid).first
  39. end
  40. 1 def update_status_by_guid(guid, is_read_status)
  41. 5 notification = get_by_guid(guid)
  42. 5 raise ActiveRecord::RecordNotFound unless notification
  43. 4 notification.set_read_state(is_read_status)
  44. 4 true
  45. end
  46. 1 def notify(object, recipient_user_ids)
  47. 3624 notification_types(object).each {|type| type.notify(object, recipient_user_ids) }
  48. end
  49. 1 private
  50. 1 def notification_types(object)
  51. 2046 NOTIFICATION_TYPES.fetch(object.class, [])
  52. end
  53. end

app/services/photo_service.rb

97.3% lines covered

37 relevant lines. 36 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PhotoService
  3. 1 def initialize(user=nil, deny_raw_files=true)
  4. 31 @user = user
  5. 31 @deny_raw_files = deny_raw_files
  6. end
  7. 1 def visible_photo(photo_guid)
  8. 10 Photo.owned_or_visible_by_user(@user).where(guid: photo_guid).first
  9. end
  10. 1 def create_from_params_and_file(base_params, uploaded_file)
  11. 21 photo_params = build_params(base_params)
  12. 21 raise RuntimeError if @deny_raw_files && !confirm_uploaded_file_settings(uploaded_file)
  13. 20 photo_params[:user_file] = uploaded_file
  14. 20 photo = @user.build_post(:photo, photo_params)
  15. 16 raise RuntimeError unless photo.save
  16. 16 send_messages(photo, photo_params)
  17. 16 update_profile_photo(photo) if photo_params[:set_profile_photo]
  18. 16 photo
  19. end
  20. 1 private
  21. 1 def build_params(base_params)
  22. 21 photo_params = base_params.permit(:pending, :set_profile_photo, aspect_ids: [])
  23. 21 if base_params.permit(:aspect_ids)[:aspect_ids] == "all"
  24. 7 photo_params[:aspect_ids] = @user.aspects.map(&:id)
  25. 14 elsif photo_params[:aspect_ids].is_a?(Hash)
  26. photo_params[:aspect_ids] = params[:photo][:aspect_ids].values
  27. end
  28. 21 photo_params
  29. end
  30. 1 def confirm_uploaded_file_settings(uploaded_file)
  31. 13 unless uploaded_file.is_a?(ActionDispatch::Http::UploadedFile) || uploaded_file.is_a?(Rack::Test::UploadedFile)
  32. 1 return false
  33. end
  34. 12 return false if uploaded_file.original_filename.empty?
  35. 12 return false if uploaded_file.content_type.empty?
  36. 12 true
  37. end
  38. 1 def send_messages(photo, photo_params)
  39. 16 send_to_streams(photo, photo_params) unless photo.pending && photo.public?
  40. 16 @user.dispatch_post(photo, to: photo_params[:aspect_ids]) unless photo.pending
  41. end
  42. 1 def update_profile_photo(photo)
  43. 3 @user.update_profile(photo: photo)
  44. end
  45. 1 def send_to_streams(photo, photo_params)
  46. 16 aspects = @user.aspects_from_ids(photo_params[:aspect_ids])
  47. 16 @user.add_to_streams(photo, aspects)
  48. end
  49. end

app/services/poll_participation_service.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PollParticipationService
  3. 1 def initialize(user)
  4. 13 @user = user
  5. end
  6. 1 def vote(post_id, answer_id)
  7. 14 answer = PollAnswer.find(answer_id)
  8. 11 @user.participate_in_poll!(target(post_id), answer) if target(post_id)
  9. end
  10. 1 private
  11. 1 def target(post_id)
  12. 20 @target ||= @user.find_visible_shareable_by_id(Post, post_id) || raise(ActiveRecord::RecordNotFound.new)
  13. end
  14. end

app/services/post_service.rb

96.0% lines covered

50 relevant lines. 48 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PostService
  3. 1 def initialize(user=nil)
  4. 715 @user = user
  5. end
  6. 1 def find(id)
  7. 38 if user
  8. 35 user.find_visible_shareable_by_id(Post, id)
  9. else
  10. 3 Post.find_by_id_and_public(id, true)
  11. end
  12. end
  13. 1 def find!(id_or_guid)
  14. 646 if user
  15. 605 find_non_public_by_guid_or_id_with_user!(id_or_guid)
  16. else
  17. 41 find_public!(id_or_guid)
  18. end
  19. end
  20. 1 def present_json
  21. PostPresenter.new(post, user)
  22. end
  23. 1 def present_interactions_json
  24. PostInteractionPresenter.new(post, user)
  25. end
  26. 1 def mark_user_notifications(post_id)
  27. 20 return unless user
  28. 11 mark_comment_reshare_like_notifications_read(post_id)
  29. 11 mark_mention_notifications_read(post_id)
  30. 11 mark_like_on_comment_notifications_read(post_id)
  31. end
  32. 1 def destroy(post_id, private_allowed=true)
  33. 12 post = if private_allowed
  34. 10 find_non_public_by_guid_or_id_with_user!(post_id)
  35. else
  36. 2 find_public!(post_id)
  37. end
  38. 7 raise Diaspora::NotMine unless post.author == user.person
  39. 4 user.retract(post)
  40. end
  41. 1 def mentionable_in_comment(post_id, query)
  42. 17 post = find!(post_id)
  43. 15 Person
  44. .allowed_to_be_mentioned_in_a_comment_to(post)
  45. .where.not(id: user.person_id)
  46. .find_by_substring(query)
  47. .sort_for_mention_suggestion(post, user)
  48. .for_json
  49. .limit(15)
  50. end
  51. 1 private
  52. 1 attr_reader :user
  53. 1 def find_public!(id_or_guid)
  54. 43 Post.where(post_key(id_or_guid) => id_or_guid).first.tap do |post|
  55. 43 raise ActiveRecord::RecordNotFound, "could not find a post with id #{id_or_guid}" unless post
  56. 42 raise Diaspora::NonPublic unless post.public?
  57. end
  58. end
  59. 1 def find_non_public_by_guid_or_id_with_user!(id_or_guid)
  60. 615 user.find_visible_shareable_by_id(Post, id_or_guid, key: post_key(id_or_guid)).tap do |post|
  61. 615 raise ActiveRecord::RecordNotFound, "could not find a post with id #{id_or_guid} for user #{user.id}" unless post
  62. end
  63. end
  64. # We can assume a guid is at least 16 characters long as we have guids set to hex(8) since we started using them.
  65. 1 def post_key(id_or_guid)
  66. 658 id_or_guid.to_s.length < 16 ? :id : :guid
  67. end
  68. 1 def mark_comment_reshare_like_notifications_read(post_id)
  69. 11 Notification.where(recipient_id: user.id, target_type: "Post", target_id: post_id, unread: true)
  70. .update_all(unread: false)
  71. end
  72. 1 def mark_mention_notifications_read(post_id)
  73. 11 mention_ids = Mention.where(
  74. mentions_container_id: post_id,
  75. mentions_container_type: "Post",
  76. person_id: user.person_id
  77. ).ids
  78. 11 mention_ids.concat(mentions_in_comments_for_post(post_id).pluck(:id))
  79. Notification.where(recipient_id: user.id, target_type: "Mention", target_id: mention_ids, unread: true)
  80. 11 .update_all(unread: false) if mention_ids.any?
  81. end
  82. 1 def mentions_in_comments_for_post(post_id)
  83. 11 Mention
  84. .joins("INNER JOIN comments ON mentions_container_id = comments.id AND mentions_container_type = 'Comment'")
  85. .where(comments: {commentable_id: post_id, commentable_type: "Post"})
  86. end
  87. 1 def mark_like_on_comment_notifications_read(post_id)
  88. 11 Notification.where(recipient_id: user.id, target_type: "Comment",
  89. target_id: Comment.where(commentable_id: post_id, author_id: user.person.id),
  90. unread: true).update_all(unread: false) # rubocop:disable Rails/SkipsModelValidations
  91. end
  92. end

app/services/reshare_service.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ReshareService
  3. 1 def initialize(user=nil)
  4. 69 @user = user
  5. end
  6. 1 def create(post_id)
  7. 41 post = post_service.find!(post_id)
  8. 38 post = post.absolute_root if post.is_a? Reshare
  9. 38 user.reshare!(post)
  10. end
  11. 1 def find_for_post(post_id)
  12. 28 reshares = post_service.find!(post_id).reshares
  13. 22 user ? reshares.order(Arel.sql("author_id = #{user.person.id} DESC")) : reshares
  14. end
  15. 1 private
  16. 1 attr_reader :user
  17. 1 def post_service
  18. 69 @post_service ||= PostService.new(user)
  19. end
  20. end

app/services/status_message_creation_service.rb

100.0% lines covered

51 relevant lines. 51 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class StatusMessageCreationService
  3. 1 include Rails.application.routes.url_helpers
  4. 1 def initialize(user)
  5. 97 @user = user
  6. end
  7. 1 def create(params)
  8. 97 validate_content(params)
  9. 95 build_status_message(params).tap do |status_message|
  10. 95 load_aspects(params[:aspect_ids]) unless status_message.public?
  11. 90 add_attachments(status_message, params)
  12. 90 status_message.save
  13. 90 process(status_message, params[:services])
  14. end
  15. end
  16. 1 private
  17. 1 attr_reader :user, :aspects
  18. 1 def validate_content(params)
  19. 97 raise MissingContent unless params[:status_message][:text].present? || params[:photos].present?
  20. end
  21. 1 def build_status_message(params)
  22. 95 public = params[:public] || false
  23. 95 user.build_post(:status_message, params[:status_message].merge(public: public))
  24. end
  25. 1 def add_attachments(status_message, params)
  26. 90 add_location(status_message, params[:location_address], params[:location_coords])
  27. 90 add_poll(status_message, params)
  28. 90 add_photos(status_message, params[:photos])
  29. end
  30. 1 def add_location(status_message, address, coordinates)
  31. 90 status_message.build_location(address: address, coordinates: coordinates) if address.present?
  32. end
  33. 1 def add_poll(status_message, params)
  34. 90 if params[:poll_question].present?
  35. 11 status_message.build_poll(question: params[:poll_question])
  36. 11 [*params[:poll_answers]].each do |poll_answer|
  37. 33 answer = status_message.poll.poll_answers.build(answer: poll_answer)
  38. 33 answer.poll = status_message.poll
  39. end
  40. end
  41. end
  42. 1 def add_photos(status_message, photos)
  43. 90 if photos.present?
  44. 40 status_message.photos << Photo.where(id: photos, author_id: status_message.author_id)
  45. 40 status_message.photos.each do |photo|
  46. 54 photo.public = status_message.public
  47. 54 photo.pending = false
  48. end
  49. end
  50. end
  51. 1 def load_aspects(aspect_ids)
  52. 42 @aspects = user.aspects_from_ids(aspect_ids)
  53. 42 raise BadAspectsIDs if aspects.empty?
  54. end
  55. 1 def process(status_message, services)
  56. 90 add_to_streams(status_message) unless status_message.public?
  57. 88 dispatch(status_message, services)
  58. end
  59. 1 def add_to_streams(status_message)
  60. 37 user.add_to_streams(status_message, aspects)
  61. 49 status_message.photos.each {|photo| user.add_to_streams(photo, aspects) }
  62. end
  63. 1 def dispatch(status_message, services)
  64. 88 receiving_services = services ? Service.titles(services) : []
  65. 88 status_message.filter_mentions # this is only required until changes from #6818 are deployed on every pod
  66. 88 user.dispatch_post(status_message,
  67. url: short_post_url(status_message.guid, host: AppConfig.environment.url),
  68. service_types: receiving_services)
  69. end
  70. 1 class BadAspectsIDs < RuntimeError
  71. end
  72. 1 class MissingContent < RuntimeError
  73. end
  74. end

app/services/tag_following_service.rb

91.67% lines covered

24 relevant lines. 22 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class TagFollowingService
  3. 1 def initialize(user=nil)
  4. 32 @user = user
  5. end
  6. 1 def create(name)
  7. 18 name_normalized = ActsAsTaggableOn::Tag.normalize(name)
  8. 18 raise ArgumentError, "Name field null or empty" if name_normalized.blank?
  9. 11 tag = ActsAsTaggableOn::Tag.find_or_create_by(name: name_normalized)
  10. 11 raise DuplicateTag if @user.tag_followings.exists?(tag_id: tag.id)
  11. 9 tag_following = @user.tag_followings.new(tag_id: tag.id)
  12. 9 raise "Can't process tag entity" unless tag_following.save
  13. 9 tag
  14. end
  15. 1 def find(name)
  16. name_normalized = ActsAsTaggableOn::Tag.normalize(name)
  17. ActsAsTaggableOn::Tag.find_or_create_by(name: name_normalized)
  18. end
  19. 1 def destroy(id)
  20. 5 tag_following = @user.tag_followings.find_by!(tag_id: id)
  21. 4 tag_following.destroy
  22. end
  23. 1 def destroy_by_name(name)
  24. 5 name_normalized = ActsAsTaggableOn::Tag.normalize(name)
  25. 5 followed_tag = @user.followed_tags.find_by!(name: name_normalized)
  26. 2 destroy(followed_tag.id)
  27. end
  28. 1 def index
  29. 5 @user.followed_tags
  30. end
  31. 1 class DuplicateTag < RuntimeError; end
  32. end

app/uploaders/exported_photos.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class ExportedPhotos < SecureUploader
  6. def store_dir
  7. "uploads/users"
  8. end
  9. def extension_allowlist
  10. %w[zip]
  11. end
  12. def filename
  13. "diaspora_#{model.username}_photos_#{secure_token}#{extension}"
  14. end
  15. end

app/uploaders/exported_user.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class ExportedUser < SecureUploader
  6. def store_dir
  7. "uploads/users"
  8. end
  9. def extension_allowlist
  10. %w[gz zip json]
  11. end
  12. def filename
  13. "diaspora_#{model.username}_data_#{secure_token}#{extension}"
  14. end
  15. end

app/uploaders/processed_image.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class ProcessedImage < CarrierWave::Uploader::Base
  6. 1 include CarrierWave::MiniMagick
  7. 1 def store_dir
  8. 2660 "uploads/images"
  9. end
  10. 1 def extension_allowlist
  11. 2440 %w[jpg jpeg png gif webp]
  12. end
  13. 1 def filename
  14. 5856 model.random_string + File.extname(@filename) if @filename
  15. end
  16. 1 version :thumb_small do
  17. 1 process resize_to_fill: [50, 50, combine_options: {unsharp: "1.5x1+0.7+0.02"}]
  18. end
  19. 1 version :thumb_medium do
  20. 1 process resize_to_limit: [100, 100, combine_options: {unsharp: "1.5x1+0.7+0.02"}]
  21. end
  22. 1 version :thumb_large do
  23. 1 process resize_to_limit: [300, 1500]
  24. end
  25. 1 version :scaled_full do
  26. 1 process resize_to_limit: [700, nil]
  27. end
  28. end

app/uploaders/secure_uploader.rb

0.0% lines covered

10 relevant lines. 0 lines covered and 10 lines missed.
    
  1. # frozen_string_literal: true
  2. class SecureUploader < CarrierWave::Uploader::Base
  3. protected
  4. def extension
  5. ".#{file.filename.split('.').drop(1).join('.')}" if file.present? && file.respond_to?(:filename)
  6. end
  7. def secure_token(bytes=16)
  8. var = :"@#{mounted_as}_secure_token"
  9. model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.urlsafe_base64(bytes))
  10. end
  11. end

app/uploaders/unprocessed_image.rb

100.0% lines covered

29 relevant lines. 29 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 class UnprocessedImage < CarrierWave::Uploader::Base
  6. 1 include CarrierWave::MiniMagick
  7. 1 def store_dir
  8. 6316 "uploads/images"
  9. end
  10. 1 def extension_allowlist
  11. 3451 %w[jpg jpeg png gif webp]
  12. end
  13. 1 def filename
  14. 8257 model.random_string + extension if @filename
  15. end
  16. 1 def extension
  17. 8257 needs_converting? ? ".webp" : File.extname(@filename)
  18. end
  19. 1 def needs_converting?
  20. 8263 extname = File.extname(@filename)
  21. 8263 %w[.webp .gif].exclude?(extname) && !model.keep_original_format
  22. end
  23. 1 process :basic_process
  24. 1 def basic_process
  25. 7 manipulate! do |img|
  26. 6 img.combine_options do |i|
  27. 6 i.auto_orient
  28. 6 i.strip
  29. end
  30. 6 img = yield(img) if block_given?
  31. 6 img.format("webp") if needs_converting?
  32. 6 img
  33. end
  34. end
  35. 1 version :thumb_small
  36. 1 version :thumb_medium
  37. 1 version :thumb_large
  38. 1 version :scaled_full do
  39. 1 process :get_version_dimensions
  40. end
  41. 1 def get_version_dimensions
  42. 6 model.width, model.height = `identify -format "%wx%h " #{file.path}`.split(/x/)
  43. end
  44. end

app/workers/archive_base.rb

93.75% lines covered

16 relevant lines. 15 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class ArchiveBase < Base
  7. 1 sidekiq_options queue: :low
  8. 1 include Diaspora::Logging
  9. 1 def perform(*args)
  10. 7 if currently_running_archive_jobs >= AppConfig.settings.archive_jobs_concurrency.to_i
  11. 1 logger.info "Already the maximum number of parallel archive jobs running, " \
  12. "scheduling #{self.class}:#{args} in 5 minutes."
  13. 1 self.class.perform_in(5.minutes + rand(30), *args)
  14. else
  15. 6 perform_archive_job(*args)
  16. end
  17. end
  18. 1 private
  19. 1 def perform_archive_job(_args)
  20. raise NotImplementedError, "You must override perform_archive_job"
  21. end
  22. 1 def currently_running_archive_jobs
  23. 7 Sidekiq::Workers.new.count do |process_id, thread_id, work|
  24. 3 !(Process.pid.to_s == process_id.split(":")[1] && Thread.current.object_id.to_s(36) == thread_id) &&
  25. ArchiveBase.subclasses.map(&:to_s).include?(work["payload"]["class"])
  26. end
  27. rescue Redis::CannotConnectError
  28. # If code gets to this point and there is no Redis conenction, we're
  29. # running in a Test environment and have not mocked Sidekiq::Workers, so
  30. # we're not testing the concurrency-limiting behavior.
  31. # There is no way a production pod will run into this code, as diaspora*
  32. # refuses to start without redis.
  33. 3 0
  34. end
  35. end
  36. end

app/workers/base.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class Base
  7. 1 include Sidekiq::Worker
  8. 1 sidekiq_options backtrace: (bt = AppConfig.environment.sidekiq.backtrace.get) && bt.to_i,
  9. 1 retry: (rt = AppConfig.environment.sidekiq.retry.get) && rt.to_i
  10. 1 include Diaspora::Logging
  11. end
  12. end

app/workers/check_birthday.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class CheckBirthday < Base
  4. 1 sidekiq_options queue: :low
  5. 1 def perform
  6. 6 profiles = Profile
  7. .where("EXTRACT(MONTH FROM birthday) = ?", Time.zone.today.month)
  8. .where("EXTRACT(DAY FROM birthday) = ?", Time.zone.today.day)
  9. 6 profiles.each do |profile|
  10. 40 profile.person.contacts.where(sharing: true, receiving: true).each do |contact|
  11. 28 Notifications::ContactsBirthday.notify(contact, [])
  12. end
  13. end
  14. end
  15. end
  16. end

app/workers/clean_cached_files.rb

0.0% lines covered

8 relevant lines. 0 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. module Workers
  3. class CleanCachedFiles < Base
  4. sidekiq_options queue: :low
  5. def perform
  6. CarrierWave.clean_cached_files!
  7. end
  8. end
  9. end

app/workers/cleanup_old_exports.rb

100.0% lines covered

12 relevant lines. 12 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class CleanupOldExports < Base
  4. 1 sidekiq_options queue: :low
  5. 1 def perform
  6. 4 User.where("exported_at < ?", 14.days.ago).each do |user|
  7. 1 user.remove_export = true
  8. 1 user.exported_at = nil
  9. 1 user.save
  10. end
  11. 4 User.where("exported_photos_at < ?", 14.days.ago).each do |user|
  12. 1 user.remove_exported_photos_file = true
  13. 1 user.exported_photos_at = nil
  14. 1 user.save
  15. end
  16. end
  17. end
  18. end

app/workers/cleanup_pending_photos.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class CleanupPendingPhotos < Base
  4. 1 sidekiq_options queue: :low
  5. 1 def perform
  6. 3 Photo.where(pending: true).where("created_at < ?", 1.day.ago).destroy_all
  7. end
  8. end
  9. end

app/workers/deferred_dispatch.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class DeferredDispatch < Base
  7. 1 sidekiq_options queue: :high
  8. 1 def perform(user_id, object_class_name, object_id, opts)
  9. 2316 user = User.find(user_id)
  10. 2316 object = object_class_name.constantize.find(object_id)
  11. 2315 Diaspora::Federation::Dispatcher.build(user, object, opts.deep_symbolize_keys).dispatch
  12. rescue ActiveRecord::RecordNotFound # The target got deleted before the job was run
  13. end
  14. end
  15. end

app/workers/deferred_retraction.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class DeferredRetraction < Base
  7. 1 sidekiq_options queue: :high
  8. 1 def perform(user_id, retraction_class, retraction_data, recipient_ids, opts)
  9. 6 user = User.find(user_id)
  10. 6 subscribers = Person.where(id: recipient_ids)
  11. 6 object = retraction_class.constantize.new(retraction_data.deep_symbolize_keys, subscribers)
  12. 6 Diaspora::Federation::Dispatcher.build(user, object, opts.deep_symbolize_keys).dispatch
  13. end
  14. end
  15. end

app/workers/delete_account.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class DeleteAccount < Base
  7. 1 sidekiq_options queue: :low
  8. 1 def perform(account_deletion_id)
  9. 2 account_deletion = AccountDeletion.find(account_deletion_id)
  10. 2 account_deletion.perform!
  11. end
  12. end
  13. end

app/workers/delete_post_from_service.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. 1 module Workers
  7. 1 class DeletePostFromService < Base
  8. 1 sidekiq_options queue: :high
  9. 1 def perform(service_id, opts)
  10. 1 service = Service.find_by_id(service_id)
  11. 1 service.delete_from_service(opts.deep_symbolize_keys)
  12. end
  13. end
  14. end

app/workers/export_photos.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class ExportPhotos < Base
  7. 1 sidekiq_options queue: :low
  8. 1 def perform(user_id)
  9. 3 @user = User.find(user_id)
  10. 3 @user.perform_export_photos!
  11. 3 if @user.reload.exported_photos_file.present?
  12. 1 ExportMailer.export_photos_complete_for(@user).deliver_now
  13. else
  14. 2 ExportMailer.export_photos_failure_for(@user).deliver_now
  15. end
  16. end
  17. end
  18. end

app/workers/export_user.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class ExportUser < ArchiveBase
  7. 1 private
  8. 1 def perform_archive_job(user_id)
  9. 6 user = User.find(user_id)
  10. 6 user.perform_export!
  11. 6 if user.reload.export.present?
  12. 1 ExportMailer.export_complete_for(user).deliver_now
  13. else
  14. 5 ExportMailer.export_failure_for(user).deliver_now
  15. end
  16. end
  17. end
  18. end

app/workers/fetch_profile_photo.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class FetchProfilePhoto < Base
  7. 1 sidekiq_options queue: :medium
  8. 1 def perform(user_id, service_id, fallback_image_url = nil)
  9. 4 service = Service.find(service_id)
  10. 4 image_url = service.profile_photo_url
  11. 4 image_url ||= fallback_image_url
  12. 4 return unless image_url
  13. 3 user = User.find(user_id)
  14. 3 @photo = Photo.diaspora_initialize(:author => user.person, :image_url => image_url, :pending => true)
  15. 3 @photo.save!
  16. 3 profile_params = {:image_url => @photo.url(:thumb_large),
  17. :image_url_medium => @photo.url(:thumb_medium),
  18. :image_url_small => @photo.url(:thumb_small)}
  19. 3 user.update_profile(profile_params)
  20. end
  21. end
  22. end

app/workers/fetch_public_posts.rb

80.0% lines covered

5 relevant lines. 4 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class FetchPublicPosts < Base
  7. 1 sidekiq_options queue: :medium
  8. 1 def perform(diaspora_id)
  9. Diaspora::Fetcher::Public.new.fetch!(diaspora_id)
  10. end
  11. end
  12. end

app/workers/fetch_webfinger.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class FetchWebfinger < Base
  7. 1 sidekiq_options queue: :urgent
  8. 1 def perform(account)
  9. 2 person = Person.find_or_fetch_by_identifier(account)
  10. # also, schedule to fetch a few public posts from that person
  11. 1 Diaspora::Fetcher::Public.queue_for(person)
  12. rescue DiasporaFederation::Discovery::DiscoveryError
  13. # Ignored
  14. end
  15. end
  16. end

app/workers/gather_o_embed_data.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. 1 module Workers
  7. 1 class GatherOEmbedData < Base
  8. 1 sidekiq_options queue: :medium
  9. 1 def perform(post_id, url, retry_count=1)
  10. 8 post = Post.find(post_id)
  11. 7 post.o_embed_cache = OEmbedCache.find_or_create_by(url: url)
  12. 7 post.save
  13. rescue ActiveRecord::RecordNotFound
  14. # User created a post and deleted it right afterwards before we
  15. # we had a chance to run the job.
  16. # On the other hand sometimes the job runs before the Post is
  17. # fully persisted. So we just reduce the amount of retries.
  18. 1 GatherOEmbedData.perform_in(1.minute, post_id, url, retry_count+1) unless retry_count > 3
  19. end
  20. end
  21. end

app/workers/gather_open_graph_data.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. 1 module Workers
  7. 1 class GatherOpenGraphData < Base
  8. 1 sidekiq_options queue: :medium
  9. 1 def perform(post_id, url, retry_count=1)
  10. 8 post = Post.find(post_id)
  11. 7 post.open_graph_cache = OpenGraphCache.find_or_create_by(url: url)
  12. 7 post.save
  13. rescue ActiveRecord::RecordNotFound
  14. # User created a post and deleted it right afterwards before we
  15. # we had a chance to run the job.
  16. # On the other hand sometimes the job runs before the Post is
  17. # fully persisted. So we just reduce the amount of retries.
  18. 1 GatherOpenGraphData.perform_in(1.minute, post_id, url, retry_count+1) unless retry_count > 3
  19. end
  20. end
  21. end

app/workers/import_user.rb

0.0% lines covered

9 relevant lines. 0 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. module Workers
  3. class ImportUser < ArchiveBase
  4. private
  5. def perform_archive_job(user_id)
  6. user = User.find(user_id)
  7. ImportService.new.import_by_user(user)
  8. end
  9. end
  10. end

app/workers/mail/also_commented.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class AlsoCommented < NotifierBase
  5. end
  6. end
  7. end

app/workers/mail/comment_on_post.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class CommentOnPost < NotifierBase
  5. end
  6. end
  7. end

app/workers/mail/confirm_email.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class ConfirmEmail < NotifierBase
  5. end
  6. end
  7. end

app/workers/mail/contacts_birthday.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class ContactsBirthday < NotifierBase
  5. end
  6. end
  7. end

app/workers/mail/csrf_token_fail.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class CsrfTokenFail < NotifierBase
  5. end
  6. end
  7. end

app/workers/mail/invite_email.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 module Mail
  7. 1 class InviteEmail < Base
  8. 1 sidekiq_options queue: :low
  9. 1 def perform(emails, inviter_id, options={})
  10. 1 EmailInviter.new(emails, User.find(inviter_id), options).send!
  11. end
  12. end
  13. end
  14. end

app/workers/mail/liked.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class Liked < NotifierBase
  5. 1 def perform(*args)
  6. 6 super
  7. rescue ActiveRecord::RecordNotFound => e
  8. 2 logger.warn("failed to send liked notification mail: #{e.message}")
  9. 2 raise e unless e.message.start_with?("Couldn't find Like with")
  10. end
  11. end
  12. end
  13. end

app/workers/mail/liked_comment.rb

0.0% lines covered

6 relevant lines. 0 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. module Workers
  3. module Mail
  4. class LikedComment < Liked
  5. end
  6. end
  7. end

app/workers/mail/mentioned.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 module Mail
  7. 1 class Mentioned < NotifierBase
  8. end
  9. end
  10. end

app/workers/mail/mentioned_in_comment.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class MentionedInComment < NotifierBase
  5. end
  6. end
  7. end

app/workers/mail/notifier_base.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class NotifierBase < Base
  5. 1 sidekiq_options queue: :low
  6. 1 def perform(*args)
  7. 653 Notifier.send_notification(self.class.name.demodulize.underscore, *args).deliver_now
  8. end
  9. end
  10. end
  11. end

app/workers/mail/private_message.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 module Mail
  7. 1 class PrivateMessage < NotifierBase
  8. end
  9. end
  10. end

app/workers/mail/report_worker.rb

83.33% lines covered

6 relevant lines. 5 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class ReportWorker < Base
  5. 1 sidekiq_options queue: :low
  6. 1 def perform(report_id)
  7. ReportMailer.new_report(report_id).each(&:deliver_now)
  8. end
  9. end
  10. end
  11. end

app/workers/mail/reshared.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 module Mail
  4. 1 class Reshared < NotifierBase
  5. end
  6. end
  7. end

app/workers/mail/started_sharing.rb

100.0% lines covered

3 relevant lines. 3 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 module Mail
  7. 1 class StartedSharing < NotifierBase
  8. end
  9. end
  10. end

app/workers/post_to_service.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. #
  6. 1 module Workers
  7. 1 class PostToService < Base
  8. 1 sidekiq_options queue: :medium
  9. 1 def perform(service_id, post_id, url)
  10. 1 service = Service.find_by_id(service_id)
  11. 1 post = Post.find_by_id(post_id)
  12. 1 service.post(post, url)
  13. end
  14. end
  15. end

app/workers/process_photo.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class ProcessPhoto < Base
  7. 1 sidekiq_options queue: :low
  8. 1 def perform(id)
  9. 488 photo = Photo.find(id)
  10. 487 unprocessed_image = photo.unprocessed_image
  11. 487 return false if photo.processed? || unprocessed_image.path.try(:include?, ".gif")
  12. 485 photo.processed_image.store!(unprocessed_image)
  13. 485 photo.save!
  14. rescue ActiveRecord::RecordNotFound # Deleted before the job was run
  15. # Ignored
  16. end
  17. end
  18. end

app/workers/publish_to_hub.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class PublishToHub < Base
  7. 1 sidekiq_options queue: :medium
  8. 1 def perform(sender_atom_url)
  9. 397 Pubsubhubbub.new(AppConfig.environment.pubsub_server.get).publish(sender_atom_url)
  10. end
  11. end
  12. end

app/workers/queue_users_for_removal.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class QueueUsersForRemoval < Base
  7. 1 sidekiq_options queue: :low
  8. 1 def perform
  9. # Queue users for removal due to inactivity
  10. 8 if AppConfig.settings.maintenance.remove_old_users.enable?
  11. 6 users = User.where("last_seen < ? and locked_at is null and remove_after is null",
  12. 6 Time.now - (AppConfig.settings.maintenance.remove_old_users.after_days.to_i).days)
  13. .order(:last_seen)
  14. .limit(AppConfig.settings.maintenance.remove_old_users.limit_removals_to_per_day)
  15. # deliver to be closed emails to account holders
  16. # and queue accounts for closing to sidekiq
  17. # for those who have not signed in, skip warning and queue removal
  18. # in +1 days
  19. 6 users.each do |user|
  20. 3 if user.sign_in_count > 0
  21. 2 remove_at = Time.now + AppConfig.settings.maintenance.remove_old_users.warn_days.to_i.days
  22. else
  23. 1 remove_at = Time.now
  24. end
  25. 3 user.flag_for_removal(remove_at)
  26. 3 if user.sign_in_count > 0
  27. # send a warning
  28. 2 Maintenance.account_removal_warning(user).deliver_now
  29. end
  30. 3 Workers::RemoveOldUser.perform_in(remove_at+1.day, user.id)
  31. end
  32. end
  33. end
  34. end
  35. end

app/workers/receive_base.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class ReceiveBase < Base
  4. 1 sidekiq_options queue: :urgent
  5. 1 include Diaspora::Logging
  6. # don't retry for errors that will fail again
  7. 1 def filter_errors_for_retry
  8. 139 yield
  9. rescue DiasporaFederation::Entity::ValidationError,
  10. DiasporaFederation::Parsers::BaseParser::InvalidRootNode,
  11. DiasporaFederation::Entity::InvalidEntityName,
  12. DiasporaFederation::Entity::UnknownEntity,
  13. DiasporaFederation::Entities::Signable::PublicKeyNotFound,
  14. DiasporaFederation::Entities::Signable::SignatureVerificationFailed,
  15. DiasporaFederation::Entities::Participation::ParentNotLocal,
  16. DiasporaFederation::Federation::Receiver::InvalidSender,
  17. DiasporaFederation::Federation::Receiver::NotPublic,
  18. DiasporaFederation::Salmon::SenderKeyNotFound,
  19. DiasporaFederation::Salmon::InvalidEnvelope,
  20. DiasporaFederation::Salmon::InvalidSignature,
  21. DiasporaFederation::Salmon::InvalidDataType,
  22. DiasporaFederation::Salmon::InvalidAlgorithm,
  23. DiasporaFederation::Salmon::InvalidEncoding,
  24. Diaspora::Federation::AuthorIgnored,
  25. Diaspora::Federation::InvalidAuthor,
  26. Diaspora::Federation::RecipientClosed => e
  27. 60 logger.warn "don't retry for error: #{e.class}"
  28. end
  29. end
  30. end

app/workers/receive_local.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class ReceiveLocal < Base
  4. 1 sidekiq_options queue: :high
  5. 1 def perform(object_class_string, object_id, recipient_user_ids)
  6. 2044 object = object_class_string.constantize.find(object_id)
  7. 2044 object.receive(recipient_user_ids) if object.respond_to?(:receive)
  8. 2044 NotificationService.new.notify(object, recipient_user_ids)
  9. rescue ActiveRecord::RecordNotFound # Already deleted before the job could run
  10. end
  11. end
  12. end

app/workers/receive_private.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class ReceivePrivate < ReceiveBase
  7. 1 def perform(user_id, data)
  8. 52 filter_errors_for_retry do
  9. 52 user_private_key = User.where(id: user_id).pluck(:serialized_private_key).first
  10. 52 rsa_key = OpenSSL::PKey::RSA.new(user_private_key)
  11. 52 DiasporaFederation::Federation::Receiver.receive_private(data, rsa_key, user_id)
  12. end
  13. end
  14. end
  15. end

app/workers/receive_public.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class ReceivePublic < ReceiveBase
  7. 1 def perform(data)
  8. 87 filter_errors_for_retry do
  9. 87 DiasporaFederation::Federation::Receiver.receive_public(data)
  10. end
  11. end
  12. end
  13. end

app/workers/recheck_scheduled_pods.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class RecheckScheduledPods < Base
  4. 1 sidekiq_options queue: :low
  5. 1 def perform
  6. 1 Pod.check_scheduled!
  7. end
  8. end
  9. end

app/workers/recurring_pod_check.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class RecurringPodCheck < Base
  4. 1 sidekiq_options queue: :low
  5. 1 def perform
  6. 1 Pod.check_all!
  7. end
  8. end
  9. end

app/workers/remove_old_user.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Workers
  6. 1 class RemoveOldUser < Base
  7. 1 sidekiq_options queue: :low
  8. 1 def safe_remove_after
  9. # extra safety time to compare in addition to remove_after
  10. 2 Time.now-
  11. (AppConfig.settings.maintenance.remove_old_users.after_days.to_i).days-
  12. 2 (AppConfig.settings.maintenance.remove_old_users.warn_days.to_i).days
  13. end
  14. 1 def perform(user_id)
  15. # if user has been flagged as to be removed (see settings.maintenance.remove_old_users)
  16. # and hasn't logged in since that flag has been set, we remove the user
  17. 5 if AppConfig.settings.maintenance.remove_old_users.enable?
  18. 3 user = User.find(user_id)
  19. 3 if user.remove_after < Time.now and user.last_seen < self.safe_remove_after
  20. 1 user.close_account!
  21. end
  22. end
  23. end
  24. end
  25. end

app/workers/reset_password.rb

100.0% lines covered

5 relevant lines. 5 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class ResetPassword < Base
  4. 1 sidekiq_options queue: :urgent
  5. 1 def perform(user_id)
  6. 2 User.find(user_id).send_reset_password_instructions!
  7. end
  8. end
  9. end

app/workers/send_base.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class SendBase < Base
  4. 1 sidekiq_options queue: :medium, retry: 0
  5. 1 MAX_RETRIES = AppConfig.environment.sidekiq.retry.get.to_i
  6. 1 protected
  7. 1 def schedule_retry(retry_count, sender_id, obj_str, failed_urls)
  8. 6 if retry_count < (obj_str.start_with?("Contact") ? MAX_RETRIES + 10 : MAX_RETRIES)
  9. 3 yield(seconds_to_delay(retry_count), retry_count)
  10. else
  11. 3 logger.warn "status=abandon sender=#{sender_id} obj=#{obj_str} failed_urls='[#{failed_urls.join(', ')}]'"
  12. 3 raise MaxRetriesReached
  13. end
  14. end
  15. 1 private
  16. # based on Sidekiq::Middleware::Server::RetryJobs#seconds_to_delay
  17. 1 def seconds_to_delay(count)
  18. 24 ((count + 3)**4) + (rand(30) * (count + 1))
  19. end
  20. # send job to the dead job queue
  21. 1 class MaxRetriesReached < RuntimeError
  22. end
  23. end
  24. end

app/workers/send_private.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class SendPrivate < SendBase
  4. 1 def perform(sender_id, obj_str, targets, retry_count=0)
  5. 13 targets_to_retry = DiasporaFederation::Federation::Sender.private(sender_id, obj_str, targets)
  6. 13 return if targets_to_retry.empty?
  7. 4 schedule_retry(retry_count + 1, sender_id, obj_str, targets_to_retry.keys) do |delay, new_retry_count|
  8. 2 Workers::SendPrivate.perform_in(delay, sender_id, obj_str, targets_to_retry, new_retry_count)
  9. end
  10. end
  11. end
  12. end

app/workers/send_public.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Workers
  3. 1 class SendPublic < SendBase
  4. 1 def perform(sender_id, obj_str, urls, xml, retry_count=0)
  5. 5 urls_to_retry = DiasporaFederation::Federation::Sender.public(sender_id, obj_str, urls, xml)
  6. 5 return if urls_to_retry.empty?
  7. 2 schedule_retry(retry_count + 1, sender_id, obj_str, urls_to_retry) do |delay, new_retry_count|
  8. 1 Workers::SendPublic.perform_in(delay, sender_id, obj_str, urls_to_retry, xml, new_retry_count)
  9. end
  10. end
  11. end
  12. end

lib/account_deleter.rb

0.0% lines covered

64 relevant lines. 0 lines covered and 64 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class AccountDeleter
  6. # Things that are not removed from the database:
  7. # - Comments
  8. # - Likes
  9. # - Messages
  10. # - NotificationActors
  11. #
  12. # Given that the User in question will be tombstoned, all of the
  13. # above will come from an anonomized account (via the UI).
  14. # The deleted user will appear as "Deleted Account" in
  15. # the interface.
  16. attr_accessor :person, :user
  17. def initialize(person)
  18. self.person = person
  19. self.user = person.owner
  20. end
  21. def perform!
  22. # close person
  23. delete_standard_person_associations
  24. delete_contacts_of_me
  25. tombstone_person_and_profile
  26. close_user if user
  27. mark_account_deletion_complete
  28. end
  29. # user deletion methods
  30. def close_user
  31. remove_share_visibilities_on_contacts_posts
  32. disconnect_contacts
  33. delete_standard_user_associations
  34. delete_user_invitation_code
  35. tombstone_user
  36. end
  37. # user deletions
  38. def normal_ar_user_associates_to_delete
  39. %i[tag_followings services aspects user_preferences
  40. notifications blocks authorizations o_auth_applications pairwise_pseudonymous_identifiers]
  41. end
  42. def delete_standard_user_associations
  43. normal_ar_user_associates_to_delete.each do |asso|
  44. user.send(asso).ids.each_slice(20) do |ids|
  45. User.reflect_on_association(asso).class_name.constantize.where(id: ids).destroy_all
  46. end
  47. end
  48. end
  49. def delete_user_invitation_code
  50. InvitationCode.find_by(user_id: user.id).try(:destroy)
  51. end
  52. def normal_ar_person_associates_to_delete
  53. %i[posts photos mentions participations roles blocks conversation_visibilities]
  54. end
  55. def delete_standard_person_associations
  56. normal_ar_person_associates_to_delete.each do |asso|
  57. person.send(asso).ids.each_slice(20) do |ids|
  58. Person.reflect_on_association(asso).class_name.constantize.where(id: ids).destroy_all
  59. end
  60. end
  61. end
  62. def disconnect_contacts
  63. user.contacts.destroy_all
  64. end
  65. # Currently this would get deleted due to the db foreign key constrainsts,
  66. # but we'll keep this method here for completeness
  67. def remove_share_visibilities_on_contacts_posts
  68. ShareVisibility.for_a_user(user).find_each(batch_size: 20, &:destroy)
  69. end
  70. def tombstone_person_and_profile
  71. person.lock_access!
  72. person.clear_profile!
  73. end
  74. def tombstone_user
  75. user.clear_account!
  76. end
  77. def delete_contacts_of_me
  78. Contact.all_contacts_of_person(person).find_each(batch_size: 20, &:destroy)
  79. end
  80. def mark_account_deletion_complete
  81. AccountDeletion.find_by(person: person)&.update(completed_at: Time.now.utc)
  82. end
  83. end

lib/api/openid_connect/authorization_point/endpoint.rb

94.87% lines covered

39 relevant lines. 37 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 module AuthorizationPoint
  5. 1 class Endpoint
  6. 1 attr_accessor :app, :user, :o_auth_application, :redirect_uri, :response_type,
  7. :scopes, :request_uri, :request_object, :nonce
  8. 1 delegate :call, to: :app
  9. 1 def initialize(user)
  10. 45 @user = user
  11. 45 @app = Rack::OAuth2::Server::Authorize.new do |req, res|
  12. 44 build_from_request_object(req)
  13. 44 build_attributes(req, res)
  14. 38 if OAuthApplication.available_response_types.include? Array(req.response_type).join(" ")
  15. 38 handle_response_type(req, res)
  16. else
  17. req.unsupported_response_type!
  18. end
  19. end
  20. end
  21. 1 def build_attributes(req, res)
  22. 44 build_client(req)
  23. 44 build_redirect_uri(req, res)
  24. 42 verify_nonce(req, res)
  25. 41 build_scopes(req)
  26. end
  27. 1 def handle_response_type(_req, _res)
  28. raise NotImplementedError # Implemented by subclass
  29. end
  30. 1 private
  31. 1 def build_client(req)
  32. 44 @o_auth_application = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request!
  33. end
  34. 1 def build_redirect_uri(req, res)
  35. 44 res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@o_auth_application.redirect_uris)
  36. end
  37. 1 def verify_nonce(req, res)
  38. 42 req.invalid_request! "nonce required" if res.protocol_params_location == :fragment && req.nonce.blank?
  39. end
  40. 1 def build_scopes(req)
  41. 41 @scopes = req.scope.map {|scope|
  42. 63 scope.tap do |scope_name|
  43. req.invalid_scope! I18n.t("api.openid_connect.authorizations.new.unknown_scope", scope_name: scope_name) \
  44. 63 unless auth_scopes.include?(scope_name)
  45. end
  46. }
  47. 40 @scopes.push("public:read") unless @scopes.include?("public:read")
  48. 40 has_private_scope = @scopes.include?("private:read") || @scopes.include?("private:modify")
  49. 40 has_contacts_scope = @scopes.include? "contacts:read"
  50. 2 req.invalid_scope! I18n.t("api.openid_connect.authorizations.new.private_contacts_linkage_error") \
  51. 40 if has_private_scope && !has_contacts_scope
  52. end
  53. 1 def auth_scopes
  54. 63 Api::OpenidConnect::Authorization::SCOPES
  55. end
  56. end
  57. end
  58. end
  59. end

lib/api/openid_connect/authorization_point/endpoint_confirmation_point.rb

100.0% lines covered

39 relevant lines. 39 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 module AuthorizationPoint
  5. 1 class EndpointConfirmationPoint < Endpoint
  6. 1 def initialize(current_user, approved=false)
  7. 15 super(current_user)
  8. 15 @approved = approved
  9. end
  10. 1 def handle_response_type(req, res)
  11. 15 handle_approval(@approved, req, res)
  12. end
  13. 1 def handle_approval(approved, req, res)
  14. 15 if approved
  15. 11 approved!(req, res)
  16. else
  17. 4 req.access_denied!
  18. end
  19. end
  20. 1 def build_from_request_object(_req)
  21. # Empty
  22. end
  23. 1 private
  24. 1 def approved!(req, res)
  25. 11 auth = find_or_build_auth(req)
  26. 11 handle_approved_response_type(auth, req, res)
  27. 11 res.approve!
  28. end
  29. 1 def find_or_build_auth(req)
  30. 11 OpenidConnect::Authorization.find_or_create_by!(
  31. o_auth_application: @o_auth_application, user: @user, redirect_uri: @redirect_uri).tap do |auth|
  32. 11 auth.nonce = req.nonce
  33. 11 auth.scopes = @scopes
  34. 11 auth.save
  35. end
  36. end
  37. 1 def handle_approved_response_type(auth, req, res)
  38. 11 response_types = Array(req.response_type)
  39. 11 handle_approved_auth_code(auth, res, response_types)
  40. 11 handle_approved_access_token(auth, res, response_types)
  41. 11 handle_approved_id_token(auth, res, response_types)
  42. end
  43. 1 def handle_approved_auth_code(auth, res, response_types)
  44. 11 return unless response_types.include?(:code)
  45. 2 res.code = auth.create_code
  46. end
  47. 1 def handle_approved_access_token(auth, res, response_types)
  48. 11 return unless response_types.include?(:token)
  49. 3 res.access_token = auth.create_access_token
  50. end
  51. 1 def handle_approved_id_token(auth, res, response_types)
  52. 11 return unless response_types.include?(:id_token)
  53. 9 id_token = auth.create_id_token
  54. 9 res.id_token = id_token.to_jwt(code: res.try(:code), access_token: res.try(:access_token))
  55. end
  56. end
  57. end
  58. end
  59. end

lib/api/openid_connect/authorization_point/endpoint_start_point.rb

94.44% lines covered

18 relevant lines. 17 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 module AuthorizationPoint
  5. 1 class EndpointStartPoint < Endpoint
  6. 1 def build_from_request_object(req)
  7. 29 request_object = build_request_object(req)
  8. 29 return unless request_object
  9. 2 claims = request_object.raw_attributes.with_indifferent_access[:claims].try(:[], :userinfo).try(:keys)
  10. 2 return unless claims
  11. 1 req.update_param("scope", req.scope + claims)
  12. end
  13. 1 def handle_response_type(req, _res)
  14. 23 @response_type = req.response_type
  15. end
  16. 1 private
  17. 1 def build_request_object(req)
  18. 29 if req.request_uri.present?
  19. OpenIDConnect::RequestObject.fetch req.request_uri
  20. 29 elsif req.request.present?
  21. 2 OpenIDConnect::RequestObject.decode req.request
  22. end
  23. end
  24. end
  25. end
  26. end
  27. end

lib/api/openid_connect/error.rb

77.78% lines covered

9 relevant lines. 7 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 module Error
  5. 1 class InvalidRedirectUri < ::ArgumentError
  6. 1 def initialize
  7. super "Redirect uri contains fragment"
  8. end
  9. end
  10. 1 class InvalidSectorIdentifierUri < ::ArgumentError
  11. 1 def initialize
  12. super "Invalid sector identifier uri"
  13. end
  14. end
  15. end
  16. end
  17. end

lib/api/openid_connect/id_token.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2011 nov matake
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. # See https://github.com/nov/openid_connect_sample/blob/master/app/models/id_token.rb
  23. 1 require "uri"
  24. 1 module Api
  25. 1 module OpenidConnect
  26. 1 class IdToken
  27. 1 def initialize(authorization, nonce)
  28. 19 @authorization = authorization
  29. 19 @nonce = nonce
  30. 19 @created_at = Time.current
  31. 19 @expires_at = 30.minutes.from_now
  32. end
  33. 1 def to_jwt(options={})
  34. 19 to_response_object(options).to_jwt(OpenidConnect::IdTokenConfig::PRIVATE_KEY) do |jwt|
  35. 19 jwt.kid = :default
  36. end
  37. end
  38. 1 private
  39. 1 def to_response_object(options={})
  40. 19 OpenIDConnect::ResponseObject::IdToken.new(claims).tap do |id_token|
  41. 19 id_token.code = options[:code] if options[:code]
  42. 19 id_token.access_token = options[:access_token] if options[:access_token]
  43. end
  44. end
  45. 1 def claims
  46. 19 sub = build_sub
  47. 19 @claims ||= {
  48. iss: AppConfig.environment.url,
  49. sub: sub,
  50. aud: @authorization.o_auth_application.client_id,
  51. exp: @expires_at.to_i,
  52. iat: @created_at.to_i,
  53. auth_time: @authorization.user.current_sign_in_at.to_i,
  54. nonce: @nonce,
  55. acr: 0
  56. }
  57. end
  58. 1 def build_sub
  59. 19 Api::OpenidConnect::SubjectIdentifierCreator.create(@authorization)
  60. end
  61. end
  62. end
  63. end

lib/api/openid_connect/id_token_config.rb

72.73% lines covered

11 relevant lines. 8 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 class IdTokenConfig
  5. 1 key_file_path = File.join(Rails.root, "config", "oidc_key.pem")
  6. 1 if File.exist?(key_file_path)
  7. 1 private_key = OpenSSL::PKey::RSA.new(File.read(key_file_path))
  8. else
  9. private_key = OpenSSL::PKey::RSA.new(4096)
  10. File.write key_file_path, private_key.to_pem
  11. File.chmod(0600, key_file_path)
  12. end
  13. 1 PRIVATE_KEY = private_key
  14. 1 PUBLIC_KEY = private_key.public_key
  15. end
  16. end
  17. end

lib/api/openid_connect/protected_resource_endpoint.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2011 nov matake
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining
  5. # a copy of this software and associated documentation files (the
  6. # "Software"), to deal in the Software without restriction, including
  7. # without limitation the rights to use, copy, modify, merge, publish,
  8. # distribute, sublicense, and/or sell copies of the Software, and to
  9. # permit persons to whom the Software is furnished to do so, subject to
  10. # the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. # See https://github.com/nov/openid_connect_sample/blob/master/lib/authentication.rb#L56
  23. 1 module Api
  24. 1 module OpenidConnect
  25. 1 module ProtectedResourceEndpoint
  26. 1 attr_reader :current_token
  27. 1 def require_access_token(required_scopes)
  28. 47 raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope) unless
  29. 462 access_token?(required_scopes)
  30. end
  31. 1 def access_token?(required_scopes)
  32. 543 @current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN]
  33. raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user") unless
  34. 543 @current_token && @current_token.authorization
  35. 540 @current_token.authorization.try(:accessible?, required_scopes)
  36. end
  37. end
  38. end
  39. end

lib/api/openid_connect/subject_identifier_creator.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module OpenidConnect
  4. 1 module SubjectIdentifierCreator
  5. 1 def self.create(auth)
  6. 23 if auth.o_auth_application.ppid?
  7. 10 identifier = auth.o_auth_application.sector_identifier_uri ||
  8. URI.parse(auth.o_auth_application.redirect_uris[0]).host
  9. pairwise_pseudonymous_identifier =
  10. 10 auth.user.pairwise_pseudonymous_identifiers.find_or_create_by(identifier: identifier)
  11. 10 pairwise_pseudonymous_identifier.guid
  12. else
  13. 13 auth.user.diaspora_handle
  14. end
  15. end
  16. end
  17. end
  18. end

lib/api/openid_connect/token_endpoint.rb

96.77% lines covered

31 relevant lines. 30 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Inspired by https://github.com/nov/openid_connect_sample/blob/master/lib/token_endpoint.rb
  3. 1 module Api
  4. 1 module OpenidConnect
  5. 1 class TokenEndpoint
  6. 1 attr_accessor :app
  7. 1 delegate :call, to: :app
  8. 1 def initialize
  9. 23 @app = Rack::OAuth2::Server::Token.new do |req, res|
  10. 19 o_auth_app = retrieve_client(req)
  11. 19 if app_valid?(o_auth_app, req)
  12. 15 handle_flows(req, res)
  13. else
  14. 4 req.invalid_client!
  15. end
  16. end
  17. end
  18. 1 def handle_flows(req, res)
  19. 15 case req.grant_type
  20. when :refresh_token
  21. 3 handle_refresh_flow(req, res)
  22. when :authorization_code
  23. 12 auth = Api::OpenidConnect::Authorization.with_redirect_uri(req.redirect_uri).use_code(req.code)
  24. 12 req.invalid_grant! if auth.blank?
  25. 9 res.access_token = auth.create_access_token
  26. 9 res.access_token.refresh_token = auth.refresh_token
  27. 9 if auth.accessible? "openid"
  28. 9 id_token = auth.create_id_token
  29. 9 res.id_token = id_token.to_jwt(access_token: res.access_token)
  30. end
  31. else
  32. req.unsupported_grant_type!
  33. end
  34. end
  35. 1 def handle_refresh_flow(req, res)
  36. # Handle as if scope request was omitted even if provided.
  37. # See https://tools.ietf.org/html/rfc6749#section-6 for handling
  38. 3 auth = Api::OpenidConnect::Authorization.find_by_refresh_token req.client_id, req.refresh_token
  39. 3 if auth
  40. 2 res.access_token = auth.create_access_token
  41. else
  42. 1 req.invalid_grant!
  43. end
  44. end
  45. 1 def retrieve_client(req)
  46. 19 Api::OpenidConnect::OAuthApplication.find_by client_id: req.client_id
  47. end
  48. 1 def app_valid?(o_auth_app, req)
  49. 19 o_auth_app.try(:client_secret) == req.client_secret
  50. end
  51. end
  52. end
  53. end

lib/api/paging/index_paginator.rb

87.5% lines covered

24 relevant lines. 21 lines covered and 3 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module Paging
  4. 1 class IndexPaginator
  5. 1 def initialize(query_base, current_page, limit)
  6. 37 @query_base = query_base
  7. 37 @current_page = current_page.to_i
  8. 37 @limit = limit.to_i
  9. end
  10. 1 def page_data
  11. 102 @page_data ||= @query_base.paginate(page: @current_page, per_page: @limit)
  12. 102 @max_page = (@query_base.count * 1.0 / @limit * 1.0).ceil
  13. 102 @max_page = 1 if @max_page < 1
  14. 102 @page_data
  15. end
  16. 1 def next_page(for_url=true)
  17. 37 page_data
  18. 37 return nil if for_url && @current_page == @max_page
  19. 7 return "page=#{@current_page + 1}" if for_url
  20. 3 IndexPaginator.new(@query_base, @current_page + 1, @limit)
  21. end
  22. 1 def previous_page(for_url=true)
  23. 29 page_data
  24. 29 return nil if for_url && @current_page == 1
  25. return "page=#{@current_page - 1}" if for_url
  26. IndexPaginator.new(@query_base, @current_page - 1, @limit)
  27. end
  28. 1 def filter_parameters(parameters)
  29. parameters.delete(:page)
  30. end
  31. end
  32. end
  33. end

lib/api/paging/rest_paged_response_builder.rb

96.3% lines covered

27 relevant lines. 26 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module Paging
  4. 1 class RestPagedResponseBuilder
  5. 1 def initialize(pager, request, allowed_params=nil)
  6. 78 @pager = pager
  7. 78 @base_url = request.original_url.split("?").first if request
  8. 78 @query_parameters = if allowed_params
  9. allowed_params
  10. 78 elsif request&.query_parameters
  11. 75 request&.query_parameters
  12. else
  13. 3 {}
  14. end
  15. end
  16. 1 def response
  17. {
  18. 76 links: navigation_builder,
  19. data: @pager.page_data
  20. }
  21. end
  22. 1 private
  23. 1 def navigation_builder
  24. 76 previous_page = @pager.previous_page
  25. 76 links = {}
  26. 76 links[:previous] = link_builder(previous_page) if previous_page
  27. 76 next_page = @pager.next_page
  28. 76 links[:next] = link_builder(next_page) if next_page
  29. 76 links
  30. end
  31. 1 def link_builder(page_parameter)
  32. 84 "#{@base_url}?#{filtered_original_parameters}#{page_parameter}"
  33. end
  34. 1 def filtered_original_parameters
  35. 84 @pager.filter_parameters(@query_parameters)
  36. 84 return "" if @query_parameters.empty?
  37. 192 @query_parameters.map {|k, v| "#{k}=#{v}" }.join("&") + "&"
  38. end
  39. end
  40. end
  41. end

lib/api/paging/rest_paginator_builder.rb

95.45% lines covered

44 relevant lines. 42 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module Paging
  4. 1 class RestPaginatorBuilder
  5. 1 MAX_LIMIT = 100
  6. 1 DEFAULT_LIMIT = 15
  7. 1 def initialize(base_query, request, allow_default_page=true, default_limit=DEFAULT_LIMIT)
  8. 77 @base_query = base_query
  9. 77 @request = request
  10. 77 @allow_default_page = allow_default_page
  11. 77 @default_limit = if default_limit < MAX_LIMIT && default_limit > 0
  12. 77 default_limit
  13. else
  14. DEFAULT_LIMIT
  15. end
  16. end
  17. 1 def index_pager(params)
  18. 29 current_page = current_page_settings(params)
  19. 29 paged_response_builder(IndexPaginator.new(@base_query, current_page, limit_settings(params)))
  20. end
  21. 1 def time_pager(params, query_time_field="created_at", data_time_field=query_time_field)
  22. 48 is_descending, current_time = time_settings(params)
  23. 48 paged_response_builder(
  24. TimePaginator.new(
  25. query_base: @base_query,
  26. query_time_field: query_time_field,
  27. data_time_field: data_time_field,
  28. current_time: current_time,
  29. is_descending: is_descending,
  30. limit: limit_settings(params)
  31. )
  32. )
  33. end
  34. 1 private
  35. 1 def current_page_settings(params)
  36. 29 if params["page"]
  37. 1 requested_page = params["page"].to_i
  38. 1 requested_page = 1 if requested_page < 1
  39. 1 requested_page
  40. 28 elsif @allow_default_page
  41. 28 1
  42. else
  43. raise ActionController::ParameterMissing
  44. end
  45. end
  46. 1 def paged_response_builder(paginator)
  47. 77 Api::Paging::RestPagedResponseBuilder.new(paginator, @request)
  48. end
  49. 1 def time_settings(params)
  50. 48 time_params = params.permit("before", "after")
  51. 48 time_params["before"] = (Time.current + 1.year).iso8601 if time_params.empty? && @allow_default_page
  52. 48 raise "Missing time parameters for query building" if time_params.empty?
  53. 48 if time_params["before"]
  54. 45 is_descending = true
  55. 45 current_time = Time.iso8601(time_params["before"])
  56. else
  57. 3 is_descending = false
  58. 3 current_time = Time.iso8601(time_params["after"])
  59. end
  60. 48 [is_descending, current_time]
  61. end
  62. 1 def limit_settings(params)
  63. 77 requested_limit = params["per_page"].to_i if params["per_page"]
  64. 77 return @default_limit unless requested_limit
  65. 1 requested_limit = [1, requested_limit].max
  66. 1 [requested_limit, MAX_LIMIT].min
  67. end
  68. end
  69. end
  70. end

lib/api/paging/time_paginator.rb

96.3% lines covered

54 relevant lines. 52 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Api
  3. 1 module Paging
  4. 1 class TimePaginator
  5. 1 def initialize(opts={})
  6. 52 @query_base = opts[:query_base]
  7. 52 @query_time_field = opts[:query_time_field]
  8. 52 @data_time_field = opts[:data_time_field]
  9. 52 @current_time = opts[:current_time]
  10. 52 @limit = opts[:limit]
  11. 52 @is_descending = opts[:is_descending]
  12. 52 direction = if @is_descending
  13. 47 "<"
  14. else
  15. 5 ">"
  16. end
  17. 52 @time_query_string = "#{@query_time_field} #{direction} ?"
  18. 52 @sort_string = if @is_descending
  19. 47 "#{@query_time_field} DESC"
  20. else
  21. 5 "#{@query_time_field} ASC"
  22. end
  23. end
  24. 1 def page_data
  25. 145 return @data if @data
  26. 51 @data = @query_base.where([@time_query_string, @current_time.iso8601(3)]).limit(@limit).order(@sort_string)
  27. 153 time_data = @data.map {|d| d[@data_time_field] }.sort
  28. 51 @min_time = time_data.first
  29. 51 @max_time = time_data.last + 0.001.seconds if time_data.last
  30. 51 @data
  31. end
  32. 1 def next_page(for_url=true)
  33. 47 page_data
  34. 47 return nil unless next_time
  35. 42 return next_page_as_query_parameter if for_url
  36. TimePaginator.new(
  37. query_base: @query_base,
  38. query_time_field: @query_time_field,
  39. query_data_field: @data_time_field,
  40. current_time: next_time,
  41. is_descending: @is_descending,
  42. limit: @limit
  43. )
  44. end
  45. 1 def previous_page(for_url=true)
  46. 47 page_data
  47. 47 return nil unless previous_time
  48. 42 return previous_page_as_query_parameter if for_url
  49. TimePaginator.new(
  50. query_base: @query_base,
  51. query_time_field: @query_time_field,
  52. query_data_field: @data_time_field,
  53. current_time: previous_time,
  54. is_descending: !@is_descending,
  55. limit: @limit
  56. )
  57. end
  58. 1 def filter_parameters(parameters)
  59. 84 parameters.delete(:before)
  60. 84 parameters.delete(:after)
  61. end
  62. 1 private
  63. 1 def next_time
  64. 89 if @is_descending
  65. 83 @min_time
  66. else
  67. 6 @max_time
  68. end
  69. end
  70. 1 def previous_time
  71. 89 if @is_descending
  72. 83 @max_time
  73. else
  74. 6 @min_time
  75. end
  76. end
  77. 1 def next_page_as_query_parameter
  78. 42 if @is_descending
  79. 39 "before=#{next_time.iso8601(3)}"
  80. else
  81. 3 "after=#{next_time.iso8601(3)}"
  82. end
  83. end
  84. 1 def previous_page_as_query_parameter
  85. 42 if @is_descending
  86. 39 "after=#{previous_time.iso8601(3)}"
  87. else
  88. 3 "before=#{previous_time.iso8601(3)}"
  89. end
  90. end
  91. end
  92. end
  93. end

lib/archive_importer.rb

98.72% lines covered

78 relevant lines. 77 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveImporter
  3. 1 include ArchiveHelper
  4. 1 include Diaspora::Logging
  5. 1 attr_accessor :user
  6. 1 def initialize(archive_hash)
  7. 14 @archive_hash = archive_hash
  8. end
  9. 1 def import(opts={})
  10. 10 import_tag_followings
  11. 10 import_aspects
  12. 10 import_contacts
  13. 10 import_posts
  14. 10 import_relayables
  15. 10 import_subscriptions
  16. 10 import_others_relayables
  17. 10 import_blocks
  18. 10 import_settings if opts.fetch(:import_settings, true)
  19. 10 import_profile if opts.fetch(:import_profile, true)
  20. end
  21. 1 def find_or_create_user(attr)
  22. 6 allowed_keys = %w[email language]
  23. 6 data = convert_keys(archive_hash["user"], allowed_keys)
  24. # setting getting_started to false as the user doesn't need to see the getting started wizard
  25. 6 data.merge!(
  26. username: attr[:username],
  27. password: attr[:password],
  28. password_confirmation: attr[:password],
  29. person: {
  30. profile_attributes: profile_attributes
  31. }
  32. )
  33. 6 self.user = User.find_or_build(data)
  34. 6 user.getting_started = false
  35. 6 user.save!
  36. end
  37. 1 private
  38. 1 attr_reader :archive_hash
  39. 1 def profile_attributes
  40. 15 allowed_keys = %w[first_name last_name image_url bio gender location birthday searchable nsfw tag_string]
  41. 15 profile_data = archive_hash["user"]["profile"]["entity_data"]
  42. 15 convert_keys(profile_data, allowed_keys).tap do |attrs|
  43. 15 attrs[:public_details] = profile_data["public"]
  44. end
  45. end
  46. 1 def import_contacts
  47. 10 import_collection(contacts, ContactImporter)
  48. end
  49. 1 def set_auto_follow_back_aspect
  50. 1 name = archive_hash["user"]["auto_follow_back_aspect"]
  51. 1 return if name.nil?
  52. 1 aspect = user.aspects.find_by(name: name)
  53. 1 user.update(auto_follow_back: true, auto_follow_back_aspect: aspect) if aspect
  54. end
  55. 1 def import_aspects
  56. 10 contact_groups.each do |group|
  57. begin
  58. 4 user.aspects.create!(group.slice("name"))
  59. rescue ActiveRecord::RecordInvalid => e
  60. logger.warn "#{self}: #{e}"
  61. end
  62. end
  63. end
  64. 1 def import_posts
  65. 10 import_collection(posts, PostImporter)
  66. end
  67. 1 def import_relayables
  68. 10 import_collection(relayables, OwnRelayableImporter)
  69. end
  70. 1 def import_others_relayables
  71. 10 import_collection(others_relayables, EntityImporter)
  72. end
  73. 1 def import_blocks
  74. 10 import_collection(blocks, BlockImporter)
  75. end
  76. 1 def import_collection(collection, importer_class)
  77. 50 collection.each do |object|
  78. 30 importer_class.new(object, user).import
  79. end
  80. end
  81. 1 def import_tag_followings
  82. 10 archive_hash.fetch("user").fetch("followed_tags", []).each do |tag_name|
  83. begin
  84. 2 tag = ActsAsTaggableOn::Tag.find_or_create_by(name: tag_name)
  85. 2 user.tag_followings.create!(tag: tag)
  86. rescue ActiveRecord::RecordInvalid => e
  87. 1 logger.warn "#{self}: #{e}"
  88. end
  89. end
  90. end
  91. 1 def import_subscriptions
  92. 10 post_subscriptions.each do |post_guid|
  93. 7 post = Post.find_or_fetch_by(archive_author_diaspora_id, post_guid)
  94. 7 if post.nil?
  95. 2 logger.warn "#{self}: post with guid #{post_guid} not found, can't subscribe"
  96. 2 next
  97. end
  98. begin
  99. 5 user.participations.create!(target: post)
  100. rescue ActiveRecord::RecordInvalid => e
  101. 1 logger.warn "#{self}: #{e}"
  102. end
  103. end
  104. end
  105. 1 def import_settings
  106. 9 allowed_keys = %w[language show_community_spotlight_in_stream]
  107. 9 convert_keys(archive_hash["user"], allowed_keys).each do |key, value|
  108. 2 user.update(key => value) unless value.nil?
  109. end
  110. 9 set_auto_follow_back_aspect if archive_hash.fetch("user").fetch("auto_follow_back", false)
  111. end
  112. 1 def import_profile
  113. 9 profile_attributes.each do |key, value|
  114. 19 user.person.profile.update(key => value) unless value.nil?
  115. end
  116. end
  117. 1 def convert_keys(hash, allowed_keys)
  118. 30 hash
  119. .slice(*allowed_keys)
  120. .symbolize_keys
  121. end
  122. 1 def to_s
  123. 4 "#{self.class}:#{archive_author_diaspora_id}:#{user.diaspora_handle}"
  124. end
  125. end

lib/archive_importer/archive_helper.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveImporter
  3. 1 module ArchiveHelper
  4. 1 def posts
  5. 55 @posts ||= archive_hash.fetch("user").fetch("posts", [])
  6. end
  7. 1 def relayables
  8. 17 @relayables ||= archive_hash.fetch("user").fetch("relayables", [])
  9. end
  10. 1 def others_relayables
  11. 17 @others_relayables ||= archive_hash.fetch("others_data", {}).fetch("relayables", [])
  12. end
  13. 1 def post_subscriptions
  14. 10 archive_hash.fetch("user").fetch("post_subscriptions", [])
  15. end
  16. 1 def contacts
  17. 22 archive_hash.fetch("user").fetch("contacts", [])
  18. end
  19. 1 def contact_groups
  20. 10 @contact_groups ||= archive_hash.fetch("user").fetch("contact_groups", [])
  21. end
  22. 1 def archive_author_diaspora_id
  23. 59 @archive_author_diaspora_id ||= archive_hash.fetch("user").fetch("profile").fetch("entity_data").fetch("author")
  24. end
  25. 1 def person
  26. 10 @person ||= Person.find_or_fetch_by_identifier(archive_author_diaspora_id)
  27. end
  28. 1 def blocks
  29. 10 @blocks ||= archive_hash.fetch("user").fetch("blocks", [])
  30. end
  31. 1 def private_key
  32. 6 OpenSSL::PKey::RSA.new(serialized_private_key)
  33. end
  34. 1 def serialized_private_key
  35. 11 archive_hash.fetch("user").fetch("private_key")
  36. end
  37. end
  38. end

lib/archive_importer/block_importer.rb

93.75% lines covered

16 relevant lines. 15 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveImporter
  3. 1 class BlockImporter
  4. 1 include Diaspora::Logging
  5. 1 attr_reader :json, :user
  6. 1 def initialize(json, user)
  7. 2 @json = json
  8. 2 @user = user
  9. end
  10. 1 def import
  11. 2 p = Person.find_or_fetch_by_identifier(json)
  12. 1 migrant_person = handle_migrant_person(p)
  13. 1 user.blocks.create(person_id: migrant_person.id)
  14. rescue ActiveRecord::RecordInvalid,
  15. DiasporaFederation::Discovery::DiscoveryError => e
  16. 1 logger.warn "#{self}: #{e}"
  17. end
  18. 1 private
  19. 1 def handle_migrant_person(person)
  20. 1 return person if person.account_migration.nil?
  21. person.account_migration.newest_person
  22. end
  23. end
  24. end

lib/archive_importer/contact_importer.rb

100.0% lines covered

24 relevant lines. 24 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveImporter
  3. 1 class ContactImporter
  4. 1 include Diaspora::Logging
  5. 1 def initialize(json, user)
  6. 8 @json = json
  7. 8 @user = user
  8. end
  9. 1 attr_reader :json
  10. 1 attr_reader :user
  11. 1 def import
  12. 8 return unless json.fetch("receiving")
  13. 6 @imported_contact = create_contact
  14. 5 add_to_aspects
  15. rescue ActiveRecord::RecordInvalid => e
  16. 1 logger.warn "#{self}: #{e}"
  17. end
  18. 1 private
  19. 1 def add_to_aspects
  20. 5 json.fetch("contact_groups_membership", []).each do |group_name|
  21. 5 aspect = user.aspects.find_by(name: group_name)
  22. 5 if aspect.nil?
  23. 4 logger.warn "#{self}: aspect \"#{group_name}\" is missing"
  24. 4 next
  25. end
  26. 1 @imported_contact.aspects << aspect
  27. end
  28. end
  29. 1 def create_contact
  30. 6 person = Person.by_account_identifier(json.fetch("account_id"))
  31. # see AccountMigration#dispatch_contacts for the other half of this when the contact is sharing with the user
  32. 6 user.contacts.create!(person_id: person.id, sharing: false, receiving: true)
  33. end
  34. end
  35. end

lib/archive_importer/entity_importer.rb

100.0% lines covered

16 relevant lines. 16 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveImporter
  3. 1 class EntityImporter
  4. 1 include ArchiveValidator::EntitiesHelper
  5. 1 include Diaspora::Logging
  6. 1 def initialize(json, user)
  7. 44 @json = json
  8. 44 @user = user
  9. end
  10. 1 def import
  11. 44 self.persisted_object = Diaspora::Federation::Receive.perform(entity, skip_relaying: true)
  12. rescue DiasporaFederation::Entities::Signable::SignatureVerificationFailed,
  13. DiasporaFederation::Discovery::InvalidDocument,
  14. DiasporaFederation::Discovery::DiscoveryError,
  15. DiasporaFederation::Federation::Fetcher::NotFetchable,
  16. OwnRelayableImporter::NoParentError,
  17. ActiveRecord::RecordInvalid => e
  18. 6 logger.warn "#{self}: #{e}"
  19. 6 self.persisted_object = nil
  20. end
  21. 1 attr_reader :json
  22. 1 attr_reader :user
  23. 1 attr_accessor :persisted_object
  24. 1 def entity
  25. 32 entity_class.from_json(json)
  26. end
  27. end
  28. end

lib/archive_importer/own_entity_importer.rb

100.0% lines covered

17 relevant lines. 17 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveImporter
  3. 1 class OwnEntityImporter < EntityImporter
  4. 1 def import
  5. 35 substitute_author
  6. 35 super
  7. rescue Diaspora::Federation::InvalidAuthor
  8. 11 return if real_author == old_author_id
  9. 7 logger.warn "#{self.class}: attempt to import an entity with guid \"#{guid}\" which belongs to #{real_author}"
  10. end
  11. 1 private
  12. 1 def substitute_author
  13. 35 @old_author_id = entity_data["author"]
  14. 35 entity_data["author"] = user.diaspora_handle
  15. end
  16. 1 attr_reader :old_author_id
  17. 1 def persisted_object
  18. 20 return @persisted_object if defined?(@persisted_object)
  19. 5 @persisted_object = (instance if real_author == old_author_id)
  20. end
  21. 1 def real_author
  22. 23 instance.author.diaspora_handle
  23. end
  24. end
  25. end

lib/archive_importer/own_relayable_importer.rb

100.0% lines covered

15 relevant lines. 15 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveImporter
  3. 1 class OwnRelayableImporter < OwnEntityImporter
  4. 1 class NoParentError < RuntimeError; end
  5. 1 def entity
  6. 12 fetch_parent(symbolized_entity_data)
  7. 11 entity_class.new(symbolized_entity_data)
  8. end
  9. 1 private
  10. 1 def symbolized_entity_data
  11. 23 @symbolized_entity_data ||= entity_data.slice(*entity_class.class_props.keys.map(&:to_s)).symbolize_keys
  12. end
  13. # Copied over from DiasporaFederation::Entities::Relayable
  14. 1 def fetch_parent(data)
  15. 12 type = data.fetch(:parent_type) {
  16. 10 break entity_class::PARENT_TYPE if entity_class.const_defined?(:PARENT_TYPE)
  17. }
  18. 12 entity = Diaspora::Federation::Mappings.model_class_for(type).find_by(guid: data.fetch(:parent_guid))
  19. 12 raise NoParentError if entity.nil?
  20. 11 data[:parent] = Diaspora::Federation::Entities.related_entity(entity)
  21. end
  22. end
  23. end

lib/archive_importer/post_importer.rb

100.0% lines covered

17 relevant lines. 17 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveImporter
  3. 1 class PostImporter < OwnEntityImporter
  4. 1 include Diaspora::Logging
  5. 1 def import
  6. 20 super
  7. 20 import_subscriptions if persisted_object
  8. end
  9. 1 private
  10. 1 def substitute_author
  11. 20 super
  12. 20 return unless entity_type == "status_message"
  13. 17 entity_data["photos"].each do |photo|
  14. 5 photo["entity_data"]["author"] = user.diaspora_handle
  15. end
  16. end
  17. 1 def import_subscriptions
  18. 16 json.fetch("subscribed_users_ids", []).each do |diaspora_id|
  19. begin
  20. 5 person = Person.find_or_fetch_by_identifier(diaspora_id)
  21. 4 person = person.account_migration.newest_person unless person.account_migration.nil?
  22. 4 next if person.closed_account?
  23. # TODO: unless person.nil? import subscription: subscription import is not supported yet
  24. rescue DiasporaFederation::Discovery::DiscoveryError
  25. end
  26. end
  27. end
  28. end
  29. end

lib/archive_validator.rb

100.0% lines covered

25 relevant lines. 25 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "yajl"
  3. # ArchiveValidator checks for errors in archive. It also find non-critical problems and fixes them in the archive hash
  4. # so that the ArchiveImporter doesn't have to handle this issues. Non-critical problems found are indicated as warnings.
  5. # Also it performs necessary data fetch where required.
  6. 1 class ArchiveValidator
  7. 1 include ArchiveImporter::ArchiveHelper
  8. 1 def initialize(archive)
  9. 11 @archive = archive
  10. end
  11. 1 def validate
  12. 6 run_validators(CRITICAL_VALIDATORS, errors)
  13. 4 run_validators(NON_CRITICAL_VALIDATORS, warnings)
  14. rescue KeyError => e
  15. 1 errors.push("Missing mandatory data: #{e}")
  16. rescue Yajl::ParseError => e
  17. 1 errors.push("Bad JSON provided: #{e}")
  18. end
  19. 1 def errors
  20. 14 @errors ||= []
  21. end
  22. 1 def warnings
  23. 7 @warnings ||= []
  24. end
  25. 1 def archive_hash
  26. 40 @archive_hash ||= Yajl::Parser.new.parse(archive)
  27. end
  28. CRITICAL_VALIDATORS = [
  29. 1 SchemaValidator,
  30. AuthorPrivateKeyValidator
  31. ].freeze
  32. NON_CRITICAL_VALIDATORS = [
  33. 1 ContactsValidator,
  34. PostsValidator,
  35. RelayablesValidator,
  36. OthersRelayablesValidator
  37. ].freeze
  38. 1 private_constant :CRITICAL_VALIDATORS, :NON_CRITICAL_VALIDATORS
  39. 1 private
  40. 1 attr_reader :archive
  41. 1 def run_validators(list, messages)
  42. 10 list.each do |validator_class|
  43. 27 validator = validator_class.new(archive_hash)
  44. 25 messages.concat(validator.messages)
  45. end
  46. end
  47. end

lib/archive_validator/author_private_key_validator.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class AuthorPrivateKeyValidator < BaseValidator
  4. 1 include Diaspora::Logging
  5. 1 def validate
  6. 9 return if person.public_key.export == private_key.public_key.export
  7. 1 messages.push("Private key in the archive doesn't match the known key of #{person.diaspora_handle}")
  8. rescue DiasporaFederation::Discovery::DiscoveryError
  9. 2 logger.info "Archive author couldn't be fetched (old home pod is down?), will continue with data"\
  10. " import only"
  11. end
  12. end
  13. end

lib/archive_validator/base_validator.rb

100.0% lines covered

14 relevant lines. 14 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class BaseValidator
  4. 1 include ArchiveImporter::ArchiveHelper
  5. 1 attr_reader :archive_hash
  6. 1 def initialize(archive_hash)
  7. 132 @archive_hash = archive_hash
  8. 132 validate
  9. end
  10. 1 def messages
  11. 258 @messages ||= []
  12. end
  13. 1 def valid?
  14. 98 @valid.nil? ? messages.empty? : @valid
  15. end
  16. 1 private
  17. 1 attr_writer :valid
  18. 1 def validate; end
  19. end
  20. end

lib/archive_validator/collection_validator.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class CollectionValidator < BaseValidator
  4. # Runs validations over each element in collection and removes every element
  5. # which fails the validations. Any messages produced by the entity_validator are
  6. # concatenated to the messages of the CollectionValidator instance.
  7. 1 def validate
  8. 26 collection.keep_if do |item|
  9. 66 subvalidator = entity_validator.new(archive_hash, item)
  10. 66 messages.concat(subvalidator.messages)
  11. 66 subvalidator.valid?
  12. end
  13. end
  14. end
  15. end

lib/archive_validator/contact_validator.rb

100.0% lines covered

22 relevant lines. 22 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class ContactValidator < BaseValidator
  4. 1 def initialize(archive_hash, contact)
  5. 18 @contact = contact
  6. 18 super(archive_hash)
  7. end
  8. 1 private
  9. 1 def validate
  10. 18 handle_migrant_contact
  11. 17 self.valid = account_open?
  12. rescue DiasporaFederation::Discovery::DiscoveryError => e
  13. 1 messages.push("#{self.class}: failed to fetch person #{diaspora_id}: #{e}")
  14. 1 self.valid = false
  15. end
  16. 1 attr_reader :contact
  17. 1 def diaspora_id
  18. 26 contact.fetch("account_id")
  19. end
  20. 1 def handle_migrant_contact
  21. 18 return if person.account_migration.nil?
  22. 4 contact["account_id"] = person.account_migration.newest_person.diaspora_handle
  23. 4 @person = nil
  24. end
  25. 1 def person
  26. 39 @person ||= Person.find_or_fetch_by_identifier(diaspora_id)
  27. end
  28. 1 def account_open?
  29. 17 !person.closed_account? || (messages.push("#{self.class}: account #{diaspora_id} is closed") && false)
  30. end
  31. end
  32. end

lib/archive_validator/contacts_validator.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class ContactsValidator < CollectionValidator
  4. 1 def collection
  5. 7 contacts
  6. end
  7. 1 def entity_validator
  8. 12 ContactValidator
  9. end
  10. end
  11. end

lib/archive_validator/entities_helper.rb

100.0% lines covered

17 relevant lines. 17 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 module EntitiesHelper
  4. 1 private
  5. 1 def instance
  6. 25 @instance ||= model_class.find_by(guid: guid)
  7. end
  8. 1 def entity_type
  9. 193 json.fetch("entity_type")
  10. end
  11. 1 def entity_data
  12. 179 json.fetch("entity_data")
  13. end
  14. 1 def model_class
  15. 11 @model_class ||= Diaspora::Federation::Mappings.model_class_for(entity_type.camelize)
  16. end
  17. 1 def entity_class
  18. 95 DiasporaFederation::Entity.entity_class(entity_type)
  19. end
  20. 1 def guid
  21. 38 @guid ||= entity_data.fetch("guid")
  22. end
  23. 1 def to_s
  24. 20 "#{entity_class.class_name}:#{guid}"
  25. end
  26. end
  27. end

lib/archive_validator/others_relayables_validator.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class OthersRelayablesValidator < CollectionValidator
  4. 1 def collection
  5. 7 others_relayables
  6. end
  7. 1 def entity_validator
  8. 6 RelayableValidator
  9. end
  10. end
  11. end

lib/archive_validator/own_relayable_validator.rb

100.0% lines covered

9 relevant lines. 9 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class OwnRelayableValidator < RelayableValidator
  4. 1 private
  5. 1 def post_find_by_guid(guid)
  6. 15 super || by_guid(Post, guid)
  7. end
  8. 1 def post_find_by_poll_guid(guid)
  9. 10 super || by_guid(Poll, guid)&.status_message
  10. end
  11. 1 def by_guid(klass, guid)
  12. 23 klass.find_or_fetch_by(archive_author_diaspora_id, guid)
  13. end
  14. end
  15. end

lib/archive_validator/post_validator.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class PostValidator < BaseValidator
  4. 1 include EntitiesHelper
  5. 1 def initialize(archive_hash, post)
  6. 29 @json = post
  7. 29 super(archive_hash)
  8. end
  9. 1 private
  10. 1 def validate
  11. 29 return unless entity_type == "reshare" && entity_data["root_guid"].nil?
  12. 6 messages.push("reshare #{self} doesn't have a root, ignored")
  13. end
  14. 1 attr_reader :json
  15. end
  16. end

lib/archive_validator/posts_validator.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class PostsValidator < CollectionValidator
  4. 1 def collection
  5. 7 posts
  6. end
  7. 1 def entity_validator
  8. 27 PostValidator
  9. end
  10. end
  11. end

lib/archive_validator/relayable_validator.rb

100.0% lines covered

29 relevant lines. 29 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. # We have to validate relayables before import because during import we'll not be able to fetch parent anymore
  4. # because parent author will point to ourselves.
  5. 1 class RelayableValidator < BaseValidator
  6. 1 include EntitiesHelper
  7. 1 def initialize(archive_hash, relayable)
  8. 38 @relayable = relayable
  9. 38 super(archive_hash)
  10. end
  11. 1 private
  12. 1 def validate
  13. 38 self.valid = parent_present?
  14. end
  15. 1 attr_reader :relayable
  16. 1 alias json relayable
  17. # Common methods used by subclasses:
  18. 1 def missing_parent_message
  19. 8 messages.push("Parent entity for #{self} is missing. Impossible to import, ignoring.")
  20. end
  21. 1 def parent_present?
  22. 38 parent.present? || (missing_parent_message && false)
  23. end
  24. 1 def parent
  25. 38 @parent ||= find_parent
  26. end
  27. 1 def find_parent
  28. 38 if entity_type == "poll_participation"
  29. 13 post_find_by_poll_guid(parent_guid)
  30. else
  31. 25 post_find_by_guid(parent_guid)
  32. end
  33. end
  34. 1 def parent_guid
  35. 38 entity_data.fetch("parent_guid")
  36. end
  37. 1 def post_find_by_guid(guid)
  38. 25 posts.find {|post|
  39. 55 post.fetch("entity_data").fetch("guid") == guid
  40. }
  41. end
  42. 1 def post_find_by_poll_guid(guid)
  43. 13 posts.find {|post|
  44. 45 post.fetch("entity_data").fetch("poll", nil)&.fetch("entity_data", nil)&.fetch("guid", nil) == guid
  45. }
  46. end
  47. end
  48. end

lib/archive_validator/relayables_validator.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class RelayablesValidator < CollectionValidator
  4. 1 def collection
  5. 7 relayables
  6. end
  7. 1 def entity_validator
  8. 15 OwnRelayableValidator
  9. end
  10. end
  11. end

lib/archive_validator/schema_validator.rb

100.0% lines covered

6 relevant lines. 6 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ArchiveValidator
  3. 1 class SchemaValidator < BaseValidator
  4. 1 JSON_SCHEMA = "lib/schemas/archive-format.json"
  5. 1 def validate
  6. 6 return if JSON::Validator.validate(JSON_SCHEMA, archive_hash)
  7. 2 messages.push("Archive schema validation failed")
  8. end
  9. end
  10. end

lib/bookmarklet_renderer.rb

90.0% lines covered

20 relevant lines. 18 lines covered and 2 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class BookmarkletRenderer
  3. 1 class << self
  4. 1 def cached_name
  5. 1 @cached_name ||= if Rails.application.config.assets.compile
  6. 1 "bookmarklet.js"
  7. else
  8. Rails.application.assets_manifest.assets["bookmarklet.js"]
  9. end
  10. end
  11. 1 def cached_path
  12. 48 @cached_path ||= Rails.root.join("public", "assets", cached_name)
  13. end
  14. 1 def source
  15. 16 @source ||= Rails.application.assets["bookmarklet.js"].filename
  16. end
  17. 1 def body
  18. 16 unless File.exist?(cached_path) || Rails.application.config.assets.compile
  19. raise "Please run the rake task to compile the bookmarklet: `bin/rake assets:precompile`"
  20. end
  21. 16 compile if Rails.application.config.assets.compile
  22. 16 @body ||= File.read(cached_path)
  23. end
  24. 1 def compile
  25. 16 src = File.read(source)
  26. 16 @body = Terser.compile(src)
  27. 16 FileUtils.mkdir_p cached_path.dirname
  28. 32 File.open(cached_path, "w") {|f| f.write(@body) }
  29. end
  30. end
  31. end

lib/configuration_methods.rb

0.0% lines covered

123 relevant lines. 0 lines covered and 123 lines missed.
    
  1. # frozen_string_literal: true
  2. module Configuration
  3. KNOWN_SERVICES = %i[twitter tumblr wordpress].freeze
  4. module Methods
  5. def pod_uri
  6. return @pod_uri.dup unless @pod_uri.nil?
  7. url = environment.url.get
  8. begin
  9. @pod_uri = Addressable::URI.heuristic_parse(url)
  10. rescue
  11. puts "WARNING: pod url #{url} is not a legal URI"
  12. end
  13. @pod_uri.scheme = "https" if environment.require_ssl?
  14. @pod_uri.path = "/"
  15. @pod_uri.dup
  16. end
  17. # @param path [String]
  18. # @return [String]
  19. def url_to(path)
  20. pod_uri.tap {|uri| uri.path = path }.to_s
  21. end
  22. def bare_pod_uri
  23. pod_uri.authority.gsub('www.', '')
  24. end
  25. def configured_services
  26. return @configured_services unless @configured_services.nil?
  27. @configured_services = []
  28. KNOWN_SERVICES.each do |service|
  29. @configured_services << service if services.send(service).enable?
  30. end
  31. @configured_services
  32. end
  33. attr_writer :configured_services
  34. def show_service?(service, user)
  35. return false unless self["services.#{service}.enable"]
  36. # Return true only if 'authorized' is true or equal to user username
  37. (user && self["services.#{service}.authorized"] == user.username) ||
  38. self["services.#{service}.authorized"] == true
  39. end
  40. def local_posts_stream?(user)
  41. return true if settings.enable_local_posts_stream == "admins" &&
  42. user.admin?
  43. return true if settings.enable_local_posts_stream == "moderators" &&
  44. user.moderator?
  45. settings.enable_local_posts_stream == "everyone"
  46. end
  47. def secret_token
  48. if heroku?
  49. return ENV["SECRET_TOKEN"] if ENV["SECRET_TOKEN"]
  50. warn "FATAL: Running on Heroku with SECRET_TOKEN unset"
  51. warn " Run heroku config:add SECRET_TOKEN=#{SecureRandom.hex(40)}"
  52. abort
  53. else
  54. token_file = File.expand_path(
  55. "../config/initializers/secret_token.rb",
  56. File.dirname(__FILE__)
  57. )
  58. system "DISABLE_SPRING=1 bin/rake generate:secret_token" unless File.exist? token_file
  59. require token_file
  60. Diaspora::Application.config.secret_key_base
  61. end
  62. end
  63. def version_string
  64. return @version_string unless @version_string.nil?
  65. @version_string = version.number.to_s
  66. @version_string = "#{@version_string}-p#{git_revision[0..7]}" if git_available?
  67. @version_string
  68. end
  69. def git_available?
  70. return @git_available unless @git_available.nil?
  71. if heroku?
  72. @git_available = false
  73. else
  74. `which git`
  75. `git status 2> /dev/null` if $?.success?
  76. @git_available = $?.success?
  77. end
  78. end
  79. def git_revision
  80. get_git_info if git_available?
  81. @git_revision
  82. end
  83. def git_update
  84. get_git_info if git_available?
  85. @git_update
  86. end
  87. def rails_asset_id
  88. (git_revision || version)[0..8]
  89. end
  90. def get_redis_options
  91. redis_url = ENV["REDIS_URL"] || environment.redis.get
  92. return {} unless redis_url.present?
  93. unless redis_url.start_with?("redis://", "unix:///")
  94. warn "WARNING: Your redis url (#{redis_url}) doesn't start with redis:// or unix:///"
  95. end
  96. {url: redis_url}
  97. end
  98. def sidekiq_log
  99. path = Pathname.new environment.sidekiq.log.get
  100. path = Rails.root.join(path) unless path.absolute?
  101. path.to_s
  102. end
  103. def postgres?
  104. ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
  105. end
  106. def mysql?
  107. ActiveRecord::Base.connection.adapter_name == "Mysql2"
  108. end
  109. def bitcoin_donation_address
  110. if AppConfig.settings.bitcoin_address.present?
  111. AppConfig.settings.bitcoin_address
  112. end
  113. end
  114. private
  115. def get_git_info
  116. return if git_info_present? || !git_available?
  117. git_cmd = `git log -1 --pretty="format:%H %ci"`
  118. if git_cmd =~ /^(\w+?)\s(.+)$/
  119. @git_revision = $1
  120. @git_update = $2.strip
  121. end
  122. end
  123. def git_info_present?
  124. @git_revision || @git_update
  125. end
  126. end
  127. end

lib/connection_tester.rb

87.27% lines covered

110 relevant lines. 96 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class ConnectionTester
  3. 1 include Diaspora::Logging
  4. 1 NODEINFO_FRAGMENT = "/.well-known/nodeinfo"
  5. 1 class << self
  6. # Test the reachability of a server by the given HTTP/S URL.
  7. # In the first step, a DNS query is performed to check whether the
  8. # given name even resolves correctly.
  9. # The second step is to send a HTTP request and look at the returned
  10. # status code or any returned errors.
  11. # This function isn't intended to check for the availability of a
  12. # specific page, instead a GET request is sent to the root directory
  13. # of the server.
  14. # In the third step an attempt is made to determine the software version
  15. # used on the server, via the nodeinfo page.
  16. #
  17. # @api This is the entry point you're supposed to use for testing
  18. # connections to other diaspora-compatible servers.
  19. # @param [String] url URL
  20. # @return [Result] result object containing information about the
  21. # server and to what point the connection was successful
  22. 1 def check(url)
  23. 2 result = Result.new
  24. begin
  25. 2 ct = ConnectionTester.new(url, result)
  26. # test DNS resolving
  27. 1 ct.resolve
  28. # test HTTP request
  29. ct.request
  30. # test for the diaspora* version
  31. ct.nodeinfo
  32. rescue Failure => e
  33. 2 result_from_failure(result, e)
  34. end
  35. 2 result.freeze
  36. end
  37. 1 private
  38. # infer some attributes of the result object based on the failure
  39. 1 def result_from_failure(result, error)
  40. 2 result.error = error
  41. 2 case error
  42. when AddressFailure, DNSFailure, NetFailure
  43. 2 result.reachable = false
  44. when SSLFailure
  45. result.reachable = true
  46. result.ssl = false
  47. when HTTPFailure
  48. result.reachable = true
  49. when NodeInfoFailure
  50. result.software_version = ""
  51. end
  52. end
  53. end
  54. # @raise [AddressFailure] if the specified url is not http(s)
  55. 1 def initialize(url, result=Result.new)
  56. 25 @url ||= url
  57. 25 @result ||= result
  58. 25 @uri ||= URI.parse(@url)
  59. 2 raise AddressFailure,
  60. 25 "invalid protocol: '#{@uri.scheme.upcase}'" unless http_uri?(@uri)
  61. rescue AddressFailure => e
  62. 2 raise e
  63. rescue URI::InvalidURIError => e
  64. raise AddressFailure, e.message
  65. rescue StandardError => e
  66. unexpected_error(e)
  67. end
  68. # Perform the DNS query, the IP address will be stored in the result
  69. # @raise [DNSFailure] caused by a failure to resolve or a timeout
  70. 1 def resolve
  71. 3 @result.ip = IPSocket.getaddress(@uri.host)
  72. rescue SocketError => e
  73. 2 raise DNSFailure, "'#{@uri.host}' - #{e.message}"
  74. rescue StandardError => e
  75. unexpected_error(e)
  76. end
  77. # Perform a HTTP GET request to determine the following information
  78. # * is the host reachable
  79. # * is port 80/443 open
  80. # * is the SSL certificate valid (only on HTTPS)
  81. # * does the server return a successful HTTP status code
  82. # * is there a reasonable amount of redirects (3 by default)
  83. # (can't do a HEAD request, since that's not a defined route in the app)
  84. #
  85. # @raise [NetFailure, SSLFailure, HTTPFailure] if any of the checks fail
  86. # @return [Integer] HTTP status code
  87. 1 def request
  88. 9 with_http_connection do |http|
  89. 18 capture_response_time { handle_http_response(http.get("/")) }
  90. end
  91. rescue HTTPFailure => e
  92. 1 raise e
  93. rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
  94. 1 raise NetFailure, e.message
  95. rescue Faraday::SSLError => e
  96. 1 raise SSLFailure, e.message
  97. rescue ArgumentError, Faraday::ClientError, Faraday::ServerError => e
  98. 3 raise HTTPFailure, "#{e.class}: #{e.message}"
  99. rescue StandardError => e
  100. unexpected_error(e)
  101. end
  102. # Try to find out the version of the other servers software.
  103. # Assuming the server speaks nodeinfo
  104. #
  105. # @raise [HTTPFailure] if the document can't be fetched
  106. # @raise [NodeInfoFailure] if the document can't be parsed or is invalid
  107. 1 def nodeinfo
  108. 10 with_http_connection do |http|
  109. 10 ni_resp = http.get(NODEINFO_FRAGMENT)
  110. 9 ni_urls = find_nodeinfo_urls(ni_resp.body)
  111. 7 raise NodeInfoFailure, "No supported NodeInfo version found" if ni_urls.empty?
  112. 6 version, url = ni_urls.max
  113. 6 find_software_version(version, http.get(url).body)
  114. end
  115. rescue Faraday::ClientError => e
  116. 1 raise HTTPFailure, "#{e.class}: #{e.message}"
  117. rescue NodeInfoFailure => e
  118. 2 raise e
  119. rescue JSON::Schema::ValidationError, JSON::Schema::SchemaError, Faraday::TimeoutError => e
  120. 2 raise NodeInfoFailure, "#{e.class}: #{e.message}"
  121. rescue JSON::JSONError => e
  122. 1 raise NodeInfoFailure, e.message[0..255].encode(Encoding.default_external, undef: :replace)
  123. rescue StandardError => e
  124. unexpected_error(e)
  125. end
  126. 1 private
  127. 1 def with_http_connection
  128. 19 @http ||= Faraday.new(@url) do |c|
  129. 19 c.use Faraday::Response::RaiseError
  130. 19 c.use Faraday::FollowRedirects::Middleware, limit: 3
  131. 19 c.adapter(Faraday.default_adapter)
  132. 19 c.headers[:user_agent] = "diaspora-connection-tester"
  133. 19 c.options.timeout = 12
  134. 19 c.options.open_timeout = 6
  135. # use the configured CA
  136. 19 c.ssl.ca_file = Faraday.default_connection.ssl.ca_file
  137. end
  138. 19 yield(@http) if block_given?
  139. end
  140. 1 def http_uri?(uri)
  141. 25 uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
  142. end
  143. # request root path, measure response time
  144. # measured time may be skewed, if there are redirects
  145. #
  146. # @return [Faraday::Response]
  147. 1 def capture_response_time
  148. 9 start = Time.zone.now
  149. 9 resp = yield if block_given?
  150. 3 @result.rt = ((Time.zone.now - start) * 1000.0).to_i # milliseconds
  151. 3 resp
  152. end
  153. 1 def handle_http_response(response)
  154. 4 @result.status_code = Integer(response.status)
  155. 4 raise HTTPFailure, "unsuccessful response code: #{response.status}" unless response.success?
  156. 4 raise HTTPFailure, "redirected to other hostname: #{response.env.url}" unless @uri.host == response.env.url.host
  157. 3 @result.reachable = true
  158. 3 @result.ssl = (response.env.url.scheme == "https")
  159. end
  160. # walk the JSON document, get the actual document locations
  161. 1 def find_nodeinfo_urls(body)
  162. 9 jrd = JSON.parse(body)
  163. 8 links = jrd.fetch("links")
  164. 8 raise NodeInfoFailure, "invalid JRD: '#/links' is not an array!" unless links.is_a?(Array)
  165. 28 supported_rel_map = NodeInfo::VERSIONS.index_by {|v| "http://nodeinfo.diaspora.software/ns/schema/#{v}" }
  166. 7 links.map {|entry|
  167. 10 version = supported_rel_map[entry.fetch("rel")]
  168. 10 [version, entry.fetch("href")] if version
  169. }.compact.to_h
  170. end
  171. # walk the JSON document, find the version string
  172. 1 def find_software_version(version, body)
  173. 5 info = JSON.parse(body)
  174. 5 JSON::Validator.validate!(NodeInfo.schema(version), info)
  175. 4 sw = info.fetch("software")
  176. 4 @result.software_version = "#{sw.fetch('name')} #{sw.fetch('version')}"
  177. end
  178. 1 def unexpected_error(error)
  179. logger.error "unexpected error: #{error.class}: #{error.message}\n#{error.backtrace.first(15).join("\n")}"
  180. raise Failure, error.inspect
  181. end
  182. 1 class Failure < StandardError
  183. end
  184. 1 class AddressFailure < Failure
  185. end
  186. 1 class DNSFailure < Failure
  187. end
  188. 1 class NetFailure < Failure
  189. end
  190. 1 class SSLFailure < Failure
  191. end
  192. 1 class HTTPFailure < Failure
  193. end
  194. 1 class NodeInfoFailure < Failure
  195. end
  196. 1 Result = Struct.new(
  197. :ip, :reachable, :ssl, :status_code, :rt, :software_version, :error
  198. ) do
  199. # @!attribute ip
  200. # @return [String] resolved IP address from DNS query
  201. # @!attribute reachable
  202. # @return [Boolean] whether the host was reachable over the network
  203. # @!attribute ssl
  204. # @return [Boolean] whether the host has working ssl
  205. # @!attribute status_code
  206. # @return [Integer] HTTP status code that was returned for the HEAD request
  207. # @!attribute rt
  208. # @return [Integer] response time for the HTTP request
  209. # @!attribute software_version
  210. # @return [String] version of diaspora* as reported by nodeinfo
  211. # @!attribute error
  212. # @return [Exception] if the test is unsuccessful, this will contain
  213. # an exception of type {ConnectionTester::Failure}
  214. 1 def initialize
  215. 30 self.rt = -1
  216. end
  217. 1 def success?
  218. error.nil?
  219. end
  220. 1 def error?
  221. 20 !error.nil?
  222. end
  223. 1 def failure_message
  224. 8 "#{error.class.name}: #{error.message}" if error?
  225. end
  226. end
  227. end

lib/diaspora.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module Diaspora
  6. require "diaspora/camo"
  7. require "diaspora/exceptions"
  8. require "diaspora/exporter"
  9. require "diaspora/federated"
  10. require "diaspora/federation"
  11. require "diaspora/fetcher"
  12. require "diaspora/markdownify"
  13. require "diaspora/mentionable"
  14. require "diaspora/message_renderer"
  15. end

lib/diaspora/camo.rb

0.0% lines covered

35 relevant lines. 0 lines covered and 35 lines missed.
    
  1. # frozen_string_literal: true
  2. # implicitly requires OpenSSL
  3. module Diaspora
  4. module Camo
  5. def self.from_markdown(markdown_text)
  6. return unless markdown_text
  7. markdown_text = markdown_text.gsub(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/m) do |link|
  8. link.gsub($4, self.image_url($4))
  9. end
  10. markdown_text.gsub(/src=(['"])(.+?)\1/m) do |link|
  11. link.gsub($2, self.image_url($2))
  12. end
  13. end
  14. def self.image_url(url)
  15. return unless url
  16. return url unless self.url_eligible?(url)
  17. begin
  18. url = Addressable::URI.encode(Addressable::URI.unencode(url))
  19. rescue Addressable::URI::InvalidURIError
  20. return url
  21. end
  22. digest = OpenSSL::HMAC.hexdigest(
  23. OpenSSL::Digest.new('sha1'),
  24. AppConfig.privacy.camo.key,
  25. url
  26. )
  27. encoded_url = url.to_enum(:each_byte).map {|byte| '%02x' % byte}.join
  28. File.join(AppConfig.privacy.camo.root, digest, encoded_url)
  29. end
  30. def self.url_eligible?(url)
  31. return false unless url.start_with?('http', '//')
  32. return false if url.start_with?(AppConfig.environment.url.to_s,
  33. AppConfig.privacy.camo.root.to_s)
  34. true
  35. end
  36. end
  37. end

lib/diaspora/commentable.rb

91.67% lines covered

12 relevant lines. 11 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Diaspora
  6. 1 module Commentable
  7. 1 def self.included(model)
  8. 1 model.instance_eval do
  9. 1 has_many :comments, :as => :commentable, :dependent => :destroy
  10. end
  11. end
  12. # @return [Array<Comment>]
  13. 1 def last_three_comments
  14. 32 return [] if self.comments_count == 0
  15. # DO NOT USE .last(3) HERE. IT WILL FETCH ALL COMMENTS AND RETURN THE LAST THREE
  16. # INSTEAD OF DOING THE FOLLOWING, AS EXPECTED (THX AR):
  17. 2 self.comments.order('created_at DESC').limit(3).includes(:author => :profile).reverse
  18. end
  19. # @return [Integer]
  20. 1 def update_comments_counter
  21. 697 self.class.where(:id => self.id).
  22. update_all(:comments_count => self.comments.count)
  23. end
  24. 1 def comments_authors
  25. Person.where(id: comments.select(:author_id).distinct)
  26. end
  27. end
  28. end

lib/diaspora/entity_finder.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Diaspora
  3. 1 class EntityFinder
  4. 1 def initialize(type, guid)
  5. 33 @type = type
  6. 33 @guid = guid
  7. end
  8. 1 def class_name
  9. 34 @class_name ||= DiasporaFederation::Entity.entity_class(type).to_s.rpartition("::").last
  10. end
  11. 1 def find
  12. 34 Diaspora::Federation::Mappings.model_class_for(class_name).find_by(guid: guid)
  13. end
  14. 1 private
  15. 1 attr_reader :type, :guid
  16. end
  17. end

lib/diaspora/exceptions.rb

0.0% lines covered

8 relevant lines. 0 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module Diaspora
  6. # the post in question is not public, and that is somehow a problem
  7. class NonPublic < StandardError
  8. end
  9. # the account was closed and that should not be the case if we want
  10. # to continue
  11. class AccountClosed < StandardError
  12. end
  13. # something that should be accessed does not belong to the current user and
  14. # that prevents further execution
  15. class NotMine < StandardError
  16. end
  17. end

lib/diaspora/exporter.rb

0.0% lines covered

17 relevant lines. 0 lines covered and 17 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module Diaspora
  6. class Exporter
  7. SERIALIZED_VERSION = "2.0"
  8. def initialize(user)
  9. @user = user
  10. end
  11. def execute
  12. JSON.generate full_archive
  13. end
  14. private
  15. def full_archive
  16. {version: SERIALIZED_VERSION}
  17. .merge(Export::UserSerializer.new(@user.id).as_json)
  18. .merge(Export::OthersDataSerializer.new(@user.id).as_json)
  19. end
  20. end
  21. end

lib/diaspora/exporter/others_relayables.rb

100.0% lines covered

13 relevant lines. 13 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Diaspora
  3. 1 class Exporter
  4. # This class implements methods that allow to query relayables (comments, likes, participations,
  5. # poll_participations) of other people for posts of the given person.
  6. 1 class OthersRelayables
  7. # @param person_id [Integer] Database id of a person for whom we want to request relayalbes
  8. 1 def initialize(person_id)
  9. 31 @person_id = person_id
  10. end
  11. # Comments of other people to the person's post
  12. # @return [Comment::ActiveRecord_Relation]
  13. 1 def comments
  14. 29 Comment
  15. .where.not(author_id: person_id)
  16. .joins("INNER JOIN posts ON (commentable_type = 'Post' AND posts.id = commentable_id)")
  17. .where("posts.author_id = ?", person_id)
  18. end
  19. # Likes of other people to the person's post
  20. # @return [Like::ActiveRecord_Relation]
  21. 1 def likes
  22. 29 Like
  23. .where.not(author_id: person_id)
  24. .joins("INNER JOIN posts ON (target_type = 'Post' AND posts.id = target_id)")
  25. .where("posts.author_id = ?", person_id)
  26. end
  27. # Poll participations of other people to the person's polls
  28. # @return [PollParticipation::ActiveRecord_Relation]
  29. 1 def poll_participations
  30. 29 PollParticipation
  31. .where.not(author_id: person_id).joins(:status_message)
  32. .where("posts.author_id = ?", person_id)
  33. end
  34. 1 private
  35. 1 attr_reader :person_id
  36. end
  37. end
  38. end

lib/diaspora/federated.rb

0.0% lines covered

7 relevant lines. 0 lines covered and 7 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module Diaspora
  6. module Federated
  7. require "diaspora/federated/base"
  8. require "diaspora/federated/retraction"
  9. require "diaspora/federated/contact_retraction"
  10. end
  11. end

lib/diaspora/federated/base.rb

0.0% lines covered

12 relevant lines. 0 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. # including this module lets you federate an object at the most basic of level
  6. module Diaspora
  7. module Federated
  8. module Base
  9. # object for local recipients
  10. def object_to_receive
  11. self
  12. end
  13. # @abstract
  14. # @note this must return [Array<Person>]
  15. # @return [Array<Person>]
  16. def subscribers
  17. raise "You must override subscribers in order to enable federation on this model"
  18. end
  19. end
  20. end
  21. end

lib/diaspora/federated/contact_retraction.rb

0.0% lines covered

15 relevant lines. 0 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. class ContactRetraction < Retraction
  3. def self.entity_class
  4. DiasporaFederation::Entities::Contact
  5. end
  6. def self.retraction_data_for(target)
  7. Diaspora::Federation::Entities.build(target).to_h
  8. end
  9. def self.for(target)
  10. target.receiving = false if target.is_a?(Contact)
  11. super
  12. end
  13. def public?
  14. false
  15. end
  16. end

lib/diaspora/federated/fetchable.rb

100.0% lines covered

11 relevant lines. 11 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Diaspora
  3. 1 module Federated
  4. 1 module Fetchable
  5. 1 extend ActiveSupport::Concern
  6. 1 module ClassMethods
  7. 1 def find_or_fetch_by(diaspora_id, guid)
  8. 30 instance = find_by(guid: guid)
  9. 30 return instance if instance.present?
  10. 16 DiasporaFederation::Federation::Fetcher.fetch_public(diaspora_id, to_s, guid)
  11. 10 find_by(guid: guid)
  12. rescue DiasporaFederation::Federation::Fetcher::NotFetchable
  13. 6 nil
  14. end
  15. end
  16. end
  17. end
  18. end

lib/diaspora/federated/generator.rb

94.44% lines covered

18 relevant lines. 17 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Diaspora
  3. 1 module Federated
  4. 1 class Generator
  5. 1 include Diaspora::Logging
  6. 1 def initialize(user, target)
  7. 1107 @user = user
  8. 1107 @target = target
  9. end
  10. 1 def create!(options={})
  11. 1089 relayable = build(options)
  12. 1089 if relayable.save!
  13. 1077 logger.info "user:#{@user.id} dispatching #{relayable.class}:#{relayable.guid}"
  14. 1077 Diaspora::Federation::Dispatcher.defer_dispatch(@user, relayable)
  15. 1077 relayable
  16. end
  17. end
  18. 1 def build(options={})
  19. 1103 self.class.federated_class.new(options.merge(relayable_options).merge(author_id: @user.person.id))
  20. end
  21. 1 protected
  22. 1 def relayable_options
  23. raise NotImplementedError, "You must override relayable_options"
  24. end
  25. end
  26. end
  27. end

lib/diaspora/federated/retraction.rb

0.0% lines covered

54 relevant lines. 0 lines covered and 54 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Retraction
  6. include Diaspora::Federated::Base
  7. include Diaspora::Logging
  8. attr_reader :subscribers, :data
  9. def initialize(data, subscribers, target=nil)
  10. @data = data
  11. @subscribers = subscribers
  12. @target = target
  13. end
  14. def self.entity_class
  15. DiasporaFederation::Entities::Retraction
  16. end
  17. def self.retraction_data_for(target)
  18. DiasporaFederation::Entities::Retraction.new(
  19. target_guid: target.guid,
  20. target: Diaspora::Federation::Entities.related_entity(target),
  21. target_type: Diaspora::Federation::Mappings.entity_name_for(target),
  22. author: target.diaspora_handle
  23. ).to_h
  24. end
  25. def self.for(target)
  26. federation_retraction_data = retraction_data_for(target)
  27. new(federation_retraction_data, target.subscribers.select(&:remote?), target)
  28. end
  29. def defer_dispatch(user, include_target_author=true)
  30. subscribers = dispatch_subscribers(include_target_author)
  31. Workers::DeferredRetraction.perform_async(user.id, self.class.to_s, data.deep_stringify_keys,
  32. subscribers.map(&:id), service_opts(user).deep_stringify_keys)
  33. end
  34. def perform
  35. logger.debug "Performing retraction for #{target.class.base_class}:#{target.guid}"
  36. target.destroy!
  37. logger.info "event=retraction status=complete target=#{data[:target_type]}:#{data[:target_guid]}"
  38. end
  39. def public?
  40. data[:target][:public]
  41. end
  42. private
  43. attr_reader :target
  44. def dispatch_subscribers(include_target_author)
  45. subscribers << target.author if target.is_a?(Diaspora::Relayable) && include_target_author && target.author.remote?
  46. subscribers
  47. end
  48. def service_opts(user)
  49. return {} unless target.is_a?(StatusMessage)
  50. user.services.each_with_object(service_types: []) do |service, opts|
  51. service_opts = service.post_opts(target)
  52. if service_opts
  53. opts.merge!(service_opts)
  54. opts[:service_types] << service.class.to_s
  55. end
  56. end
  57. end
  58. end

lib/diaspora/federation.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Federation
  4. # Raised, if author is ignored by the relayable parent author
  5. class AuthorIgnored < RuntimeError
  6. end
  7. # Raised, if the author of the existing object doesn't match the received author
  8. class InvalidAuthor < RuntimeError
  9. end
  10. # Raised, if the recipient account is closed already
  11. class RecipientClosed < RuntimeError
  12. end
  13. end
  14. end
  15. require "diaspora/federation/dispatcher"
  16. require "diaspora/federation/entities"
  17. require "diaspora/federation/mappings"
  18. require "diaspora/federation/receive"

lib/diaspora/federation/dispatcher.rb

0.0% lines covered

72 relevant lines. 0 lines covered and 72 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Federation
  4. class Dispatcher
  5. include Diaspora::Logging
  6. def initialize(sender, object, opts={})
  7. @sender = sender
  8. @object = object
  9. @opts = opts
  10. end
  11. def self.build(sender, object, opts={})
  12. sender = object.try(:sender_for_dispatch) || sender
  13. if object.try(:public?)
  14. Public.new(sender, object, opts)
  15. else
  16. Private.new(sender, object, opts)
  17. end
  18. end
  19. def self.defer_dispatch(sender, object, opts={})
  20. Workers::DeferredDispatch.perform_async(sender.id, object.class.to_s, object.id, opts.deep_stringify_keys)
  21. end
  22. def dispatch
  23. deliver_to_services
  24. deliver_to_subscribers
  25. end
  26. private
  27. attr_reader :sender, :object, :opts
  28. def entity
  29. @entity ||= Entities.build(object)
  30. end
  31. def magic_envelope
  32. @magic_envelope ||= DiasporaFederation::Salmon::MagicEnvelope.new(
  33. entity, sender.diaspora_handle
  34. ).envelop(sender.encryption_key)
  35. end
  36. def deliver_to_services
  37. deliver_to_user_services if opts[:service_types]
  38. end
  39. def deliver_to_subscribers
  40. local_people, remote_people = subscribers.uniq(&:id).partition(&:local?)
  41. deliver_to_local(local_people) unless local_people.empty?
  42. deliver_to_remote(remote_people)
  43. end
  44. def deliver_to_local(people)
  45. object_to_receive = object.object_to_receive
  46. return unless object_to_receive
  47. Workers::ReceiveLocal.perform_async(object_to_receive.class.to_s, object_to_receive.id, people.map(&:owner_id))
  48. end
  49. def deliver_to_remote(_people)
  50. raise NotImplementedError, "This is an abstract base method. Implement in your subclass."
  51. end
  52. def deliver_to_user_services
  53. case object
  54. when StatusMessage
  55. each_service {|service| Workers::PostToService.perform_async(service.id, object.id, opts[:url]) }
  56. when Retraction
  57. each_service {|service| Workers::DeletePostFromService.perform_async(service.id, opts.deep_stringify_keys) }
  58. end
  59. end
  60. def each_service
  61. sender.services.where(type: opts[:service_types]).each {|service| yield(service) }
  62. end
  63. def subscribers
  64. opts[:subscribers] || subscribers_from_ids || object.subscribers
  65. end
  66. def subscribers_from_ids
  67. Person.where(id: opts[:subscriber_ids]) if opts[:subscriber_ids]
  68. end
  69. end
  70. end
  71. end
  72. require "diaspora/federation/dispatcher/private"
  73. require "diaspora/federation/dispatcher/public"

lib/diaspora/federation/dispatcher/private.rb

0.0% lines covered

21 relevant lines. 0 lines covered and 21 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Federation
  4. class Dispatcher
  5. class Private < Dispatcher
  6. private
  7. def deliver_to_remote(people)
  8. return if people.empty?
  9. Workers::SendPrivate.perform_async(sender.id, entity.to_s, targets(people))
  10. end
  11. def targets(people)
  12. active, inactive = people.partition {|person| person.pod.active? }
  13. logger.info "ignoring inactive pods: #{inactive.map(&:diaspora_handle).join(', ')}" if inactive.any?
  14. active.map {|person| [person.receive_url, encrypted_magic_envelope(person)] }.to_h
  15. end
  16. def encrypted_magic_envelope(person)
  17. DiasporaFederation::Salmon::EncryptedMagicEnvelope.encrypt(magic_envelope, person.public_key)
  18. end
  19. end
  20. end
  21. end
  22. end

lib/diaspora/federation/dispatcher/public.rb

0.0% lines covered

27 relevant lines. 0 lines covered and 27 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Federation
  4. class Dispatcher
  5. class Public < Dispatcher
  6. private
  7. def deliver_to_services
  8. deliver_to_hub if object.instance_of?(StatusMessage)
  9. super
  10. end
  11. def deliver_to_remote(people)
  12. targets = target_urls(people)
  13. return if targets.empty?
  14. Workers::SendPublic.perform_async(sender.id, entity.to_s, targets, magic_envelope.to_xml)
  15. end
  16. def target_urls(people)
  17. active, inactive = Pod.where(id: people.map(&:pod_id).uniq).partition(&:active?)
  18. logger.info "ignoring inactive pods: #{inactive.join(', ')}" if inactive.any?
  19. active.map {|pod| pod.url_to("/receive/public") }
  20. end
  21. def deliver_to_hub
  22. logger.debug "deliver to pubsubhubbub sender: #{sender.diaspora_handle}"
  23. Workers::PublishToHub.perform_async(sender.atom_url)
  24. end
  25. end
  26. end
  27. end
  28. end

lib/diaspora/federation/entities.rb

0.0% lines covered

208 relevant lines. 0 lines covered and 208 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Federation
  4. module Entities
  5. def self.build(entity)
  6. public_send(Mappings.builder_for(entity), entity)
  7. end
  8. def self.post(post)
  9. case post
  10. when StatusMessage
  11. status_message(post)
  12. when Reshare
  13. reshare(post)
  14. else
  15. raise ArgumentError, "unknown post-class: #{post.class}"
  16. end
  17. end
  18. def self.account_deletion(account_deletion)
  19. DiasporaFederation::Entities::AccountDeletion.new(
  20. author: account_deletion.diaspora_handle
  21. )
  22. end
  23. def self.account_migration(account_migration)
  24. DiasporaFederation::Entities::AccountMigration.new(
  25. author: account_migration.sender.diaspora_handle,
  26. profile: profile(account_migration.new_person.profile),
  27. remote_photo_path: account_migration.remote_photo_path,
  28. signature: account_migration.signature
  29. )
  30. end
  31. def self.block(block)
  32. DiasporaFederation::Entities::Contact.new(
  33. author: block.user.diaspora_handle,
  34. recipient: block.person.diaspora_handle,
  35. sharing: false,
  36. following: false,
  37. blocking: Block.exists?(user: block.user, person: block.person)
  38. )
  39. end
  40. def self.comment(comment)
  41. DiasporaFederation::Entities::Comment.new(
  42. {
  43. author: comment.diaspora_handle,
  44. guid: comment.guid,
  45. parent_guid: comment.post.guid,
  46. text: comment.text,
  47. created_at: comment.created_at,
  48. edited_at: comment.signature&.additional_data&.[]("edited_at"),
  49. author_signature: comment.signature.try(:author_signature),
  50. parent: related_entity(comment.post)
  51. },
  52. comment.signature.try(:order),
  53. comment.signature.try(:additional_data) || {}
  54. )
  55. end
  56. def self.contact(contact)
  57. DiasporaFederation::Entities::Contact.new(
  58. author: contact.user.diaspora_handle,
  59. recipient: contact.person.diaspora_handle,
  60. sharing: contact.receiving,
  61. following: contact.receiving,
  62. blocking: Block.exists?(user: contact.user, person: contact.person)
  63. )
  64. end
  65. def self.conversation(conversation)
  66. DiasporaFederation::Entities::Conversation.new(
  67. author: conversation.diaspora_handle,
  68. guid: conversation.guid,
  69. subject: conversation.subject,
  70. created_at: conversation.created_at,
  71. participants: conversation.participant_handles,
  72. messages: conversation.messages.map {|message| message(message) }
  73. )
  74. end
  75. def self.like(like)
  76. DiasporaFederation::Entities::Like.new(
  77. {
  78. author: like.diaspora_handle,
  79. guid: like.guid,
  80. parent_guid: like.target.guid,
  81. positive: like.positive,
  82. parent_type: Mappings.entity_name_for(like.target),
  83. author_signature: like.signature.try(:author_signature),
  84. parent: related_entity(like.target)
  85. },
  86. like.signature.try(:order),
  87. like.signature.try(:additional_data) || {}
  88. )
  89. end
  90. def self.location(location)
  91. DiasporaFederation::Entities::Location.new(
  92. address: location.address,
  93. lat: location.lat,
  94. lng: location.lng
  95. )
  96. end
  97. def self.message(message)
  98. DiasporaFederation::Entities::Message.new(
  99. author: message.diaspora_handle,
  100. guid: message.guid,
  101. text: message.text,
  102. created_at: message.created_at,
  103. conversation_guid: message.conversation.guid
  104. )
  105. end
  106. def self.participation(participation)
  107. DiasporaFederation::Entities::Participation.new(
  108. author: participation.diaspora_handle,
  109. guid: participation.guid,
  110. parent_guid: participation.target.guid,
  111. parent_type: Mappings.entity_name_for(participation.target)
  112. )
  113. end
  114. def self.photo(photo)
  115. DiasporaFederation::Entities::Photo.new(
  116. author: photo.diaspora_handle,
  117. guid: photo.guid,
  118. public: photo.public,
  119. created_at: photo.created_at,
  120. remote_photo_path: photo.remote_photo_path,
  121. remote_photo_name: photo.remote_photo_name,
  122. text: photo.text,
  123. status_message_guid: photo.status_message_guid,
  124. height: photo.height,
  125. width: photo.width
  126. )
  127. end
  128. def self.poll(poll)
  129. DiasporaFederation::Entities::Poll.new(
  130. guid: poll.guid,
  131. question: poll.question,
  132. poll_answers: poll.poll_answers.map {|answer| poll_answer(answer) }
  133. )
  134. end
  135. def self.poll_answer(poll_answer)
  136. DiasporaFederation::Entities::PollAnswer.new(
  137. guid: poll_answer.guid,
  138. answer: poll_answer.answer
  139. )
  140. end
  141. def self.poll_participation(poll_participation)
  142. DiasporaFederation::Entities::PollParticipation.new(
  143. {
  144. author: poll_participation.diaspora_handle,
  145. guid: poll_participation.guid,
  146. parent_guid: poll_participation.poll.guid,
  147. poll_answer_guid: poll_participation.poll_answer.guid,
  148. author_signature: poll_participation.signature.try(:author_signature),
  149. parent: related_entity(poll_participation.poll)
  150. },
  151. poll_participation.signature.try(:order),
  152. poll_participation.signature.try(:additional_data) || {}
  153. )
  154. end
  155. def self.profile(profile)
  156. DiasporaFederation::Entities::Profile.new(
  157. author: profile.diaspora_handle,
  158. edited_at: profile.updated_at,
  159. first_name: profile.first_name,
  160. last_name: profile.last_name,
  161. image_url: profile.image_url,
  162. image_url_medium: profile.image_url_medium,
  163. image_url_small: profile.image_url_small,
  164. birthday: profile.birthday,
  165. gender: profile.gender,
  166. bio: profile.bio,
  167. location: profile.location,
  168. searchable: profile.searchable,
  169. nsfw: profile.nsfw,
  170. tag_string: profile.tag_string,
  171. public: profile.public_details
  172. )
  173. end
  174. def self.reshare(reshare)
  175. DiasporaFederation::Entities::Reshare.new(
  176. root_author: reshare.root_diaspora_id,
  177. root_guid: reshare.root_guid,
  178. author: reshare.diaspora_handle,
  179. guid: reshare.guid,
  180. created_at: reshare.created_at
  181. )
  182. end
  183. def self.retraction(retraction)
  184. retraction.class.entity_class.new(retraction.data)
  185. end
  186. def self.status_message(status_message)
  187. DiasporaFederation::Entities::StatusMessage.new(
  188. author: status_message.diaspora_handle,
  189. guid: status_message.guid,
  190. text: status_message.text,
  191. photos: status_message.photos.map {|photo| photo(photo) },
  192. location: status_message.location ? location(status_message.location) : nil,
  193. poll: status_message.poll ? poll(status_message.poll) : nil,
  194. public: status_message.public,
  195. created_at: status_message.created_at,
  196. provider_display_name: status_message.provider_display_name
  197. )
  198. end
  199. def self.related_entity(entity)
  200. DiasporaFederation::Entities::RelatedEntity.new(
  201. author: entity.author.diaspora_handle,
  202. local: entity.author.local?,
  203. public: entity.respond_to?(:public?) && entity.public?,
  204. parent: entity.respond_to?(:parent) ? related_entity(entity.parent) : nil
  205. )
  206. end
  207. end
  208. end
  209. end

lib/diaspora/federation/mappings.rb

0.0% lines covered

74 relevant lines. 0 lines covered and 74 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Federation
  4. module Mappings
  5. # rubocop:disable Metrics/CyclomaticComplexity
  6. # used in Diaspora::Federation::Receive
  7. def self.receiver_for(federation_entity)
  8. case federation_entity
  9. when DiasporaFederation::Entities::AccountMigration then :account_migration
  10. when DiasporaFederation::Entities::Comment then :comment
  11. when DiasporaFederation::Entities::Contact then :contact
  12. when DiasporaFederation::Entities::Conversation then :conversation
  13. when DiasporaFederation::Entities::Like then :like
  14. when DiasporaFederation::Entities::Message then :message
  15. when DiasporaFederation::Entities::Participation then :participation
  16. when DiasporaFederation::Entities::Photo then :photo
  17. when DiasporaFederation::Entities::PollParticipation then :poll_participation
  18. when DiasporaFederation::Entities::Profile then :profile
  19. when DiasporaFederation::Entities::Reshare then :reshare
  20. when DiasporaFederation::Entities::StatusMessage then :status_message
  21. else not_found(federation_entity.class)
  22. end
  23. end
  24. # used in Diaspora::Federation::Entities
  25. def self.builder_for(diaspora_entity)
  26. case diaspora_entity
  27. when AccountMigration then :account_migration
  28. when AccountDeletion then :account_deletion
  29. when Block then :block
  30. when Comment then :comment
  31. when Contact then :contact
  32. when Conversation then :conversation
  33. when Like then :like
  34. when Message then :message
  35. when Participation then :participation
  36. when Photo then :photo
  37. when PollParticipation then :poll_participation
  38. when Profile then :profile
  39. when Reshare then :reshare
  40. when Retraction then :retraction
  41. when ContactRetraction then :retraction
  42. when StatusMessage then :status_message
  43. else not_found(diaspora_entity.class)
  44. end
  45. end
  46. def self.model_class_for(entity_name)
  47. case entity_name
  48. when "Comment" then Comment
  49. when "Conversation" then Conversation
  50. when "Like" then Like
  51. when "Participation" then Participation
  52. when "PollParticipation" then PollParticipation
  53. when "Photo" then Photo
  54. when "Poll" then Poll
  55. when "Post" then Post
  56. when "Person" then Person # TODO: deprecated
  57. when "Reshare" then Post
  58. when "StatusMessage" then Post
  59. else not_found(entity_name)
  60. end
  61. end
  62. def self.entity_name_for(model)
  63. case model
  64. when Comment then "Comment"
  65. when Like then "Like"
  66. when Participation then "Participation"
  67. when PollParticipation then "PollParticipation"
  68. when Photo then "Photo"
  69. when Post then "Post"
  70. else not_found(model.class)
  71. end
  72. end
  73. # rubocop:enable Metrics/CyclomaticComplexity
  74. private_class_method def self.not_found(key)
  75. raise DiasporaFederation::Entity::UnknownEntity, "unknown entity: #{key}"
  76. end
  77. end
  78. end
  79. end

lib/diaspora/federation/receive.rb

0.0% lines covered

320 relevant lines. 0 lines covered and 320 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Federation
  4. module Receive
  5. extend Diaspora::Logging
  6. def self.perform(entity, opts={})
  7. public_send(Mappings.receiver_for(entity), entity, opts)
  8. end
  9. def self.handle_closed_recipient(sender, recipient)
  10. return unless recipient.closed_account?
  11. entity = recipient.person.account_migration || recipient.person.account_deletion
  12. Diaspora::Federation::Dispatcher.build(recipient, entity, subscribers: [sender]).dispatch if entity.present?
  13. raise Diaspora::Federation::RecipientClosed
  14. end
  15. def self.account_deletion(entity)
  16. person = author_of(entity)
  17. AccountDeletion.create!(person: person) unless AccountDeletion.where(person: person).exists?
  18. rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
  19. raise e unless AccountDeletion.where(person: person).exists?
  20. logger.warn "ignoring error on receive AccountDeletion:#{entity.author}: #{e.class}: #{e.message}"
  21. end
  22. def self.account_migration(entity, opts)
  23. old_person = author_of(entity)
  24. profile = profile(entity.profile, opts)
  25. return if AccountMigration.exists?(old_person: old_person, new_person: profile.person)
  26. AccountMigration.create!(
  27. old_person: old_person,
  28. new_person: profile.person,
  29. remote_photo_path: entity.remote_photo_path
  30. ).tap do |migration|
  31. migration.signature = entity.signature if old_person.local?
  32. migration.save!
  33. end
  34. rescue StandardError => e
  35. raise e unless AccountMigration.exists?(old_person: old_person, new_person: profile.person)
  36. logger.warn "ignoring error on receive #{entity}: #{e.class}: #{e.message}"
  37. nil
  38. end
  39. def self.comment(entity, opts)
  40. receive_relayable(Comment, entity, opts) do
  41. Comment.new(
  42. author: author_of(entity),
  43. guid: entity.guid,
  44. created_at: entity.created_at,
  45. text: entity.text,
  46. commentable: Post.find_by(guid: entity.parent_guid)
  47. )
  48. end
  49. end
  50. def self.contact(entity, _opts)
  51. recipient = Person.find_by(diaspora_handle: entity.recipient).owner
  52. if entity.sharing
  53. Contact.create_or_update_sharing_contact(recipient, author_of(entity))
  54. else
  55. recipient.disconnected_by(author_of(entity))
  56. nil
  57. end
  58. end
  59. def self.conversation(entity, _opts)
  60. author = author_of(entity)
  61. ignore_existing_guid(Conversation, entity.guid, author) do
  62. Conversation.create!(
  63. author: author,
  64. guid: entity.guid,
  65. subject: entity.subject,
  66. created_at: entity.created_at,
  67. participant_handles: entity.participants,
  68. messages: entity.messages.map {|message| build_message(message) }
  69. )
  70. end
  71. end
  72. def self.like(entity, opts)
  73. receive_relayable(Like, entity, opts) do
  74. Like.new(
  75. author: author_of(entity),
  76. guid: entity.guid,
  77. positive: entity.positive,
  78. target: Mappings.model_class_for(entity.parent_type).find_by(guid: entity.parent_guid)
  79. )
  80. end
  81. end
  82. def self.message(entity, _opts)
  83. ignore_existing_guid(Message, entity.guid, author_of(entity)) do
  84. build_message(entity).tap(&:save!)
  85. end
  86. end
  87. def self.participation(entity, _opts)
  88. author = author_of(entity)
  89. ignore_existing_guid(Participation, entity.guid, author) do
  90. Participation.create!(
  91. author: author,
  92. guid: entity.guid,
  93. target: Mappings.model_class_for(entity.parent_type).find_by(guid: entity.parent_guid)
  94. )
  95. end
  96. end
  97. def self.photo(entity, _opts)
  98. author = author_of(entity)
  99. persisted_photo = load_from_database(Photo, entity.guid, author)
  100. if persisted_photo
  101. persisted_photo.tap do |photo|
  102. photo.update(
  103. text: entity.text,
  104. public: entity.public,
  105. created_at: entity.created_at,
  106. remote_photo_path: entity.remote_photo_path,
  107. remote_photo_name: entity.remote_photo_name,
  108. status_message_guid: entity.status_message_guid,
  109. height: entity.height,
  110. width: entity.width
  111. )
  112. end
  113. else
  114. save_photo(entity)
  115. end
  116. end
  117. def self.poll_participation(entity, opts)
  118. receive_relayable(PollParticipation, entity, opts) do
  119. PollParticipation.new(
  120. author: author_of(entity),
  121. guid: entity.guid,
  122. poll: Poll.find_by(guid: entity.parent_guid),
  123. poll_answer_guid: entity.poll_answer_guid
  124. )
  125. end
  126. end
  127. def self.profile(entity, _opts)
  128. author_of(entity).profile.tap do |profile|
  129. profile.update(
  130. first_name: entity.first_name,
  131. last_name: entity.last_name,
  132. image_url: entity.image_url,
  133. image_url_medium: entity.image_url_medium,
  134. image_url_small: entity.image_url_small,
  135. birthday: entity.birthday,
  136. gender: entity.gender,
  137. bio: entity.bio,
  138. location: entity.location,
  139. searchable: entity.searchable,
  140. nsfw: entity.nsfw,
  141. tag_string: entity.tag_string,
  142. public_details: entity.public
  143. )
  144. end
  145. end
  146. def self.reshare(entity, _opts)
  147. author = author_of(entity)
  148. ignore_existing_guid(Reshare, entity.guid, author) do
  149. Reshare.create!(
  150. author: author,
  151. guid: entity.guid,
  152. created_at: entity.created_at,
  153. root_guid: entity.root_guid
  154. ).tap {|reshare| send_participation_for(reshare) }
  155. end
  156. end
  157. def self.retraction(entity, recipient_id)
  158. model_class = Diaspora::Federation::Mappings.model_class_for(entity.target_type)
  159. object = model_class.where(guid: entity.target_guid).take!
  160. case object
  161. when Person
  162. User.find(recipient_id).disconnected_by(object)
  163. when Diaspora::Relayable
  164. if object.root.author.local?
  165. root_author = object.root.author.owner
  166. retraction = Retraction.for(object)
  167. retraction.defer_dispatch(root_author, false)
  168. retraction.perform
  169. else
  170. object.destroy!
  171. end
  172. else
  173. object.destroy!
  174. end
  175. end
  176. def self.status_message(entity, _opts) # rubocop:disable Metrics/AbcSize
  177. try_load_existing_guid(StatusMessage, entity.guid, author_of(entity)) do
  178. StatusMessage.new(
  179. author: author_of(entity),
  180. guid: entity.guid,
  181. text: entity.text,
  182. public: entity.public,
  183. created_at: entity.created_at,
  184. provider_display_name: entity.provider_display_name
  185. ).tap do |status_message|
  186. status_message.location = build_location(entity.location) if entity.location
  187. status_message.poll = build_poll(entity.poll) if entity.poll
  188. status_message.photos = save_or_load_photos(entity.photos)
  189. status_message.save!
  190. send_participation_for(status_message)
  191. end
  192. end
  193. end
  194. private_class_method def self.author_of(entity)
  195. Person.by_account_identifier(entity.author)
  196. end
  197. private_class_method def self.build_location(entity)
  198. Location.new(
  199. address: entity.address,
  200. lat: entity.lat,
  201. lng: entity.lng
  202. )
  203. end
  204. private_class_method def self.build_message(entity)
  205. Message.new(
  206. author: author_of(entity),
  207. guid: entity.guid,
  208. text: entity.text,
  209. created_at: entity.created_at,
  210. conversation_guid: entity.conversation_guid
  211. )
  212. end
  213. private_class_method def self.build_poll(entity)
  214. Poll.new(
  215. guid: entity.guid,
  216. question: entity.question
  217. ).tap do |poll|
  218. poll.poll_answers = entity.poll_answers.map do |answer|
  219. PollAnswer.new(
  220. guid: answer.guid,
  221. answer: answer.answer,
  222. poll: poll
  223. )
  224. end
  225. end
  226. end
  227. private_class_method def self.save_photo(entity)
  228. Photo.create!(
  229. author: author_of(entity),
  230. guid: entity.guid,
  231. text: entity.text,
  232. public: entity.public,
  233. created_at: entity.created_at,
  234. remote_photo_path: entity.remote_photo_path,
  235. remote_photo_name: entity.remote_photo_name,
  236. status_message_guid: entity.status_message_guid,
  237. height: entity.height,
  238. width: entity.width
  239. )
  240. end
  241. private_class_method def self.save_or_load_photos(photos)
  242. photos.map do |photo|
  243. try_load_existing_guid(Photo, photo.guid, author_of(photo)) { save_photo(photo) }
  244. end
  245. end
  246. private_class_method def self.receive_relayable(klass, entity, opts)
  247. save_relayable(klass, entity) { yield }
  248. .tap {|relayable| relay_relayable(relayable) if relayable && !opts[:skip_relaying] }
  249. end
  250. private_class_method def self.save_relayable(klass, entity)
  251. ignore_existing_guid(klass, entity.guid, author_of(entity)) do
  252. yield.tap do |relayable|
  253. retract_if_author_ignored(relayable)
  254. relayable.signature = build_signature(klass, entity) if relayable.root.author.local?
  255. relayable.save!
  256. end
  257. end
  258. end
  259. # This are property names that are known by the +diaspora_federation+ library as properties but not
  260. # specially stored in our database and therefore need to be stored in the +additional_data+ field.
  261. UNKNOWN_PROPERTIES_NAMES = %i[edited_at].freeze
  262. private_constant :UNKNOWN_PROPERTIES_NAMES
  263. private_class_method def self.build_signature(klass, entity)
  264. special_additional_data = UNKNOWN_PROPERTIES_NAMES.map {|name|
  265. [name.to_s, entity.public_send(name)] if entity.respond_to?(name) && entity.signature_order.include?(name)
  266. }.compact.to_h
  267. klass.reflect_on_association(:signature).klass.new(
  268. author_signature: entity.author_signature,
  269. additional_data: entity.additional_data.merge(special_additional_data),
  270. signature_order: SignatureOrder.find_or_create_by!(order: entity.signature_order.join(" "))
  271. )
  272. end
  273. private_class_method def self.retract_if_author_ignored(relayable)
  274. root_author = relayable.root.author.owner
  275. return unless root_author && root_author.ignored_people.include?(relayable.author)
  276. retraction = Retraction.for(relayable)
  277. Diaspora::Federation::Dispatcher.build(root_author, retraction, subscribers: [relayable.author]).dispatch
  278. raise Diaspora::Federation::AuthorIgnored
  279. end
  280. private_class_method def self.relay_relayable(relayable)
  281. root_author = relayable.root.author.owner
  282. Diaspora::Federation::Dispatcher.defer_dispatch(root_author, relayable) if root_author
  283. end
  284. # check if the object already exists, otherwise save it.
  285. # if save fails (probably because of a second object received parallel),
  286. # check again if an object with the same guid already exists, but maybe has a different author.
  287. # @raise [InvalidAuthor] if the author of the existing object doesn't match
  288. private_class_method def self.ignore_existing_guid(klass, guid, author)
  289. yield unless klass.where(guid: guid, author_id: author.id).exists?
  290. rescue => e
  291. raise e unless load_from_database(klass, guid, author)
  292. logger.warn "ignoring error on receive #{klass}:#{guid}: #{e.class}: #{e.message}"
  293. nil
  294. end
  295. # try to load the object first from the DB and if not available, save it.
  296. # if save fails (probably because of a second object received parallel),
  297. # try again to load it, because it is possibly there now.
  298. # @raise [InvalidAuthor] if the author of the existing object doesn't match
  299. private_class_method def self.try_load_existing_guid(klass, guid, author)
  300. load_from_database(klass, guid, author) || yield
  301. rescue Diaspora::Federation::InvalidAuthor => e
  302. raise e # don't try loading from db twice
  303. rescue => e
  304. logger.warn "failed to save #{klass}:#{guid} (#{e.class}: #{e.message}) - try loading it from DB"
  305. load_from_database(klass, guid, author).tap do |object|
  306. raise e unless object
  307. end
  308. end
  309. # @raise [InvalidAuthor] if the author of the loaded object doesn't match
  310. private_class_method def self.load_from_database(klass, guid, author)
  311. klass.find_by(guid: guid).tap do |object|
  312. if object && object.author_id != author.id
  313. raise Diaspora::Federation::InvalidAuthor, "#{klass}:#{guid}: #{author.diaspora_handle}"
  314. end
  315. end
  316. end
  317. private_class_method def self.send_participation_for(post)
  318. return unless post.public?
  319. user = user_for_participation
  320. participation = Participation.new(target: post, author: user.person)
  321. Diaspora::Federation::Dispatcher.build(user, participation, subscribers: [post.author]).dispatch
  322. rescue => e # rubocop:disable Lint/RescueWithoutErrorClass
  323. logger.warn "failed to send participation for post #{post.guid}: #{e.class}: #{e.message}"
  324. end
  325. # Use configured admin account if available,
  326. # or use first user with admin role if available,
  327. # or use first user who isn't closed
  328. private_class_method def self.user_for_participation
  329. User.find_by(username: AppConfig.admins.account.to_s) ||
  330. Role.admins.first&.person&.owner ||
  331. User.where(locked_at: nil).first
  332. end
  333. end
  334. end
  335. end

lib/diaspora/fetcher.rb

0.0% lines covered

5 relevant lines. 0 lines covered and 5 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Fetcher
  4. require 'diaspora/fetcher/public'
  5. end
  6. end

lib/diaspora/fetcher/public.rb

0.0% lines covered

102 relevant lines. 0 lines covered and 102 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2012, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. module Diaspora; module Fetcher; class Public
  6. include Diaspora::Logging
  7. # various states that can be assigned to a person to describe where
  8. # in the process of fetching their public posts we're currently at
  9. Status_Initial = 0
  10. Status_Running = 1
  11. Status_Fetched = 2
  12. Status_Processed = 3
  13. Status_Done = 4
  14. Status_Failed = 5
  15. Status_Unfetchable = 6
  16. def self.queue_for(person)
  17. Workers::FetchPublicPosts.perform_async(person.diaspora_handle) unless person.fetch_status > Status_Initial
  18. end
  19. # perform all actions necessary to fetch the public posts of a person
  20. # with the given diaspora_id
  21. def fetch! diaspora_id
  22. @person = Person.by_account_identifier diaspora_id
  23. return unless qualifies_for_fetching?
  24. begin
  25. retrieve_and_process_posts
  26. rescue => e
  27. set_fetch_status Public::Status_Failed
  28. raise e
  29. end
  30. set_fetch_status Public::Status_Done
  31. end
  32. private
  33. # checks, that public posts for the person can be fetched,
  34. # if it is reasonable to do so, and that they have not been fetched already
  35. def qualifies_for_fetching?
  36. raise ActiveRecord::RecordNotFound unless @person.present?
  37. return false if @person.fetch_status == Public::Status_Unfetchable
  38. # local users don't need to be fetched
  39. if @person.local?
  40. set_fetch_status Public::Status_Unfetchable
  41. return false
  42. end
  43. # this record is already being worked on
  44. return false if @person.fetch_status > Public::Status_Initial
  45. # ok, let's go
  46. @person.remote? &&
  47. @person.fetch_status == Public::Status_Initial
  48. end
  49. # call the methods to fetch and process the public posts for the person
  50. # does some error logging, in case of an exception
  51. def retrieve_and_process_posts
  52. begin
  53. retrieve_posts
  54. rescue => e
  55. logger.error "unable to retrieve public posts for #{@person.diaspora_handle}"
  56. raise e
  57. end
  58. begin
  59. process_posts
  60. rescue => e
  61. logger.error "unable to process public posts for #{@person.diaspora_handle}"
  62. raise e
  63. end
  64. end
  65. # fetch the public posts of the person from their server and save the
  66. # JSON response to `@data`
  67. def retrieve_posts
  68. set_fetch_status Public::Status_Running
  69. logger.info "fetching public posts for #{@person.diaspora_handle}"
  70. resp = Faraday.get("#{@person.url}people/#{@person.guid}/stream") do |req|
  71. req.headers['Accept'] = 'application/json'
  72. req.headers['User-Agent'] = 'diaspora-fetcher'
  73. end
  74. logger.debug "fetched response: #{resp.body.to_s[0..250]}"
  75. @data = JSON.parse resp.body
  76. set_fetch_status Public::Status_Fetched
  77. end
  78. # process the public posts that were previously fetched with `retrieve_posts`
  79. # adds posts, which pass some basic sanity-checking
  80. # @see validate
  81. def process_posts
  82. @data.each do |post|
  83. next unless validate(post)
  84. logger.info "saving fetched post (#{post['guid']}) to database"
  85. logger.debug "post: #{post.to_s[0..250]}"
  86. DiasporaFederation::Federation::Fetcher.fetch_public(
  87. @person.diaspora_handle,
  88. :post,
  89. post["guid"]
  90. )
  91. rescue DiasporaFederation::Federation::Fetcher::NotFetchable => e
  92. logger.warn e.message
  93. end
  94. set_fetch_status Public::Status_Processed
  95. end
  96. # set and save the fetch status for the current person
  97. def set_fetch_status status
  98. return if @person.nil?
  99. @person.fetch_status = status
  100. @person.save
  101. end
  102. # perform various validations to make sure the post can be saved without
  103. # troubles
  104. # @see check_existing
  105. # @see check_author
  106. # @see check_public
  107. def validate post
  108. check_existing(post) && check_author(post) && check_public(post)
  109. end
  110. # hopefully there is no post with the same guid somewhere already...
  111. def check_existing post
  112. new_post = (Post.find_by_guid(post['guid']).blank?)
  113. logger.warn "a post with that guid (#{post['guid']}) already exists" unless new_post
  114. new_post
  115. end
  116. # checks if the author of the given post is actually from the person
  117. # we're currently processing
  118. def check_author post
  119. guid = post['author']['guid']
  120. equal = (guid == @person.guid)
  121. unless equal
  122. logger.warn "the author (#{guid}) does not match the person currently being processed (#{@person.guid})"
  123. end
  124. equal
  125. end
  126. # returns wether the given post is public
  127. def check_public post
  128. ispublic = (post['public'] == true)
  129. logger.warn "the post (#{post['guid']}) is not public, this is not intended..." unless ispublic
  130. ispublic
  131. end
  132. end; end; end

lib/diaspora/fields/author.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Diaspora
  3. 1 module Fields
  4. 1 module Author
  5. 1 def self.included(model)
  6. 8 model.class_eval do
  7. 8 belongs_to :author, class_name: "Person"
  8. 8 delegate :diaspora_handle, to: :author
  9. 8 validates :author, presence: true
  10. end
  11. end
  12. end
  13. end
  14. end

lib/diaspora/fields/guid.rb

0.0% lines covered

15 relevant lines. 0 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Fields
  4. module Guid
  5. # Creates a after_initialize callback which calls #set_guid
  6. def self.included(model)
  7. model.class_eval do
  8. after_initialize :set_guid
  9. validates :guid, uniqueness: true
  10. end
  11. end
  12. # @return [String] The model's guid.
  13. def set_guid
  14. self.guid = UUID.generate(:compact) if guid.blank?
  15. end
  16. end
  17. end
  18. end

lib/diaspora/fields/target.rb

100.0% lines covered

7 relevant lines. 7 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Diaspora
  3. 1 module Fields
  4. 1 module Target
  5. 1 def self.included(model)
  6. 2 model.class_eval do
  7. 2 belongs_to :target, polymorphic: true
  8. 2 validates :target_id, uniqueness: {scope: %i(target_type author_id)}
  9. end
  10. end
  11. end
  12. end
  13. end

lib/diaspora/likeable.rb

100.0% lines covered

8 relevant lines. 8 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Diaspora
  6. 1 module Likeable
  7. 1 def self.included(model)
  8. 2 model.instance_eval do
  9. 1329 has_many :likes, -> { where(positive: true) }, dependent: :delete_all, as: :target
  10. 54 has_many :dislikes, -> { where(positive: false) }, class_name: 'Like', dependent: :delete_all, as: :target
  11. end
  12. end
  13. # @return [Integer]
  14. 1 def update_likes_counter
  15. 432 self.class.where(id: self.id).
  16. update_all(likes_count: self.likes.count)
  17. end
  18. end
  19. end

lib/diaspora/logging.rb

0.0% lines covered

8 relevant lines. 0 lines covered and 8 lines missed.
    
  1. # frozen_string_literal: true
  2. # a logging mixin providing the logger
  3. module Diaspora
  4. module Logging
  5. private
  6. def logger
  7. @logger ||= ::Logging::Logger[self]
  8. end
  9. end
  10. end

lib/diaspora/markdownify.rb

0.0% lines covered

6 relevant lines. 0 lines covered and 6 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Markdownify
  4. require 'diaspora/markdownify/html'
  5. require 'diaspora/markdownify/email'
  6. end
  7. end

lib/diaspora/markdownify/email.rb

0.0% lines covered

9 relevant lines. 0 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Markdownify
  4. class Email < Redcarpet::Render::HTML
  5. def preprocess(text)
  6. Diaspora::Taggable.format_tags_for_mail text
  7. end
  8. end
  9. end
  10. end

lib/diaspora/markdownify/html.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. module Markdownify
  4. class HTML < Redcarpet::Render::HTML
  5. include ActionView::Helpers::TextHelper
  6. def autolink link, type
  7. Twitter::TwitterText::Autolink.auto_link_urls(
  8. link,
  9. url_target: "_blank",
  10. link_attribute_block: lambda {|_, attr| attr[:rel] += " noopener noreferrer" }
  11. )
  12. end
  13. end
  14. end
  15. end

lib/diaspora/mentionable.rb

0.0% lines covered

61 relevant lines. 0 lines covered and 61 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora::Mentionable
  3. # regex for finding mention markup in plain text:
  4. # "message @{user@pod.net} text"
  5. # it can also contain a name, which gets used as the link text:
  6. # "message @{User Name; user@pod.net} text"
  7. # will yield "User Name" and "user@pod.net"
  8. REGEX = /@\{(?:([^\}]+?); )?([^\} ]+)\}/
  9. # class attribute that will be added to all mention html links
  10. PERSON_HREF_CLASS = "mention hovercardable"
  11. def self.mention_attrs(mention_str)
  12. name, diaspora_id = mention_str.match(REGEX).captures
  13. [name.try(:strip).presence, diaspora_id.strip]
  14. end
  15. # takes a message text and returns the text with mentions in (html escaped)
  16. # plain text or formatted with html markup linking to user profiles.
  17. # default is html output.
  18. #
  19. # @param [String] text containing mentions
  20. # @param [Array<Person>] list of mentioned people
  21. # @param [Hash] formatting options
  22. # @return [String] formatted message
  23. def self.format(msg_text, people, opts={})
  24. people = [*people]
  25. msg_text.to_s.gsub(REGEX) {|match_str|
  26. name, diaspora_id = mention_attrs(match_str)
  27. person = people.find {|p| p.diaspora_handle == diaspora_id }
  28. "@#{ERB::Util.h(MentionsInternal.mention_link(person, name, diaspora_id, opts))}"
  29. }
  30. end
  31. # takes a message text and returns an array of people constructed from the
  32. # contained mentions
  33. #
  34. # @param [String] text containing mentions
  35. # @return [Array<Person>] array of people
  36. def self.people_from_string(msg_text)
  37. identifiers = msg_text.to_s.scan(REGEX).map {|match_str| match_str.second.strip }
  38. identifiers.compact.uniq.map {|identifier| find_or_fetch_person_by_identifier(identifier) }.compact
  39. end
  40. # takes a message text and converts mentions for people that are not in the
  41. # given array to simple markdown links, leaving only mentions for people who
  42. # will actually be able to receive notifications for being mentioned.
  43. #
  44. # @param [String] message text
  45. # @param [Array] allowed_people ids of people that are allowed to stay
  46. # @param [Boolean] absolute_links (false) render mentions with absolute links
  47. # @return [String] message text with filtered mentions
  48. def self.filter_people(msg_text, allowed_people, absolute_links: false)
  49. mentioned_ppl = people_from_string(msg_text)
  50. msg_text.to_s.gsub(REGEX) {|match_str|
  51. name, diaspora_id = mention_attrs(match_str)
  52. person = mentioned_ppl.find {|p| p.diaspora_handle == diaspora_id }
  53. if person && allowed_people.include?(person.id)
  54. match_str
  55. else
  56. "@#{MentionsInternal.profile_link(person, name, diaspora_id, absolute: absolute_links)}"
  57. end
  58. }
  59. end
  60. # Escapes special chars in mentions to not be parsed as markdown
  61. #
  62. # @param [String] text containing mentions
  63. # @return [String] escaped message
  64. def self.escape_for_markdown(msg_text)
  65. msg_text.to_s.gsub(REGEX) {|match_str|
  66. match_str.gsub("_", "\\_")
  67. }
  68. end
  69. private_class_method def self.find_or_fetch_person_by_identifier(identifier)
  70. Person.find_or_fetch_by_identifier(identifier) if Validation::Rule::DiasporaId.new.valid_value?(identifier)
  71. rescue DiasporaFederation::Discovery::DiscoveryError
  72. nil
  73. end
  74. # inline module for namespacing
  75. module MentionsInternal
  76. extend ERB::Util
  77. # output a formatted mention link as defined by the given arguments.
  78. # if the display name is blank, falls back to the person's name.
  79. # @see Diaspora::Mentions#format
  80. #
  81. # @param [Person] AR Person
  82. # @param [String] display name
  83. # @param [Hash] formatting options
  84. def self.mention_link(person, display_name, diaspora_id, opts)
  85. return display_name || diaspora_id unless person.present?
  86. display_name ||= person.name
  87. if opts[:plain_text]
  88. display_name
  89. else
  90. # rubocop:disable Rails/OutputSafety
  91. remote_or_hovercard_link = Rails.application.routes.url_helpers.person_path(person).html_safe
  92. "<a data-hovercard=\"#{remote_or_hovercard_link}\" href=\"#{remote_or_hovercard_link}\" " \
  93. "class=\"#{PERSON_HREF_CLASS}\">#{html_escape_once(display_name)}</a>".html_safe
  94. # rubocop:enable Rails/OutputSafety
  95. end
  96. end
  97. # output a markdown formatted link to the given person with the display name as the link text.
  98. # if the display name is blank, falls back to the person's name.
  99. #
  100. # @param [Person] AR Person
  101. # @param [String] display name
  102. # @param [String] diaspora_id
  103. # @param [Boolean] absolute (false) render absolute link
  104. # @return [String] markdown person link
  105. def self.profile_link(person, display_name, diaspora_id, absolute: false)
  106. return display_name || diaspora_id unless person.present?
  107. url_helper = Rails.application.routes.url_helpers
  108. "[#{display_name || person.name}](#{absolute ? url_helper.person_url(person) : url_helper.person_path(person)})"
  109. end
  110. end
  111. end

lib/diaspora/mentions_container.rb

100.0% lines covered

20 relevant lines. 20 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Diaspora
  3. 1 module MentionsContainer
  4. 1 extend ActiveSupport::Concern
  5. 1 included do
  6. 2 after_create :create_mentions
  7. 2 has_many :mentions, as: :mentions_container, dependent: :destroy
  8. end
  9. 1 def mentioned_people
  10. 2873 if persisted?
  11. 2860 mentions.includes(person: :profile).map(&:person)
  12. else
  13. 13 Diaspora::Mentionable.people_from_string(text)
  14. end
  15. end
  16. 1 def add_mention_subscribers?
  17. 1397 public?
  18. end
  19. 1 def subscribers
  20. 1397 super.tap {|subscribers|
  21. 1397 subscribers.concat(mentions.map(&:person).select(&:remote?)) if add_mention_subscribers?
  22. }
  23. end
  24. 1 def create_mentions
  25. 3289 Diaspora::Mentionable.people_from_string(text).each do |person|
  26. 345 mentions.find_or_create_by(person_id: person.id)
  27. end
  28. end
  29. 1 def message
  30. 8299 @message ||= Diaspora::MessageRenderer.new text, mentioned_people: mentioned_people
  31. end
  32. end
  33. end

lib/diaspora/message_renderer.rb

0.0% lines covered

200 relevant lines. 0 lines covered and 200 lines missed.
    
  1. # frozen_string_literal: true
  2. module Diaspora
  3. # Takes a raw message text and converts it to
  4. # various desired target formats, respecting
  5. # all possible formatting options supported
  6. # by Diaspora
  7. class MessageRenderer
  8. class Processor
  9. class << self
  10. private :new
  11. def process message, options, &block
  12. return '' if message.blank? # Optimize for empty message
  13. processor = new message, options
  14. processor.instance_exec(&block)
  15. processor.message
  16. end
  17. def normalize message
  18. message.gsub(/[\u202a\u202b]#[\u200e\u200f\u202d\u202e](\S+)\u202c/u, "#\\1")
  19. end
  20. end
  21. attr_reader :message, :options
  22. def initialize message, options
  23. @message = message
  24. @options = options
  25. end
  26. def squish
  27. @message = message.squish if options[:squish]
  28. end
  29. def append_and_truncate
  30. if options[:truncate]
  31. # TODO: Remove .dup when upgrading to Rails 6.x.
  32. @message = @message.truncate(options[:truncate] - options[:append].to_s.size).dup
  33. end
  34. @message << options[:append].to_s
  35. @message << options[:append_after_truncate].to_s
  36. end
  37. def escape
  38. if options[:escape]
  39. @message = ERB::Util.html_escape_once message
  40. end
  41. end
  42. def strip_markdown
  43. # Footnotes are not supported in text-only outputs (mail, crossposts etc)
  44. stripdown_options = options[:markdown_options].except(:footnotes)
  45. renderer = Redcarpet::Markdown.new Redcarpet::Render::StripDown, stripdown_options
  46. @message = renderer.render(message).strip
  47. end
  48. def markdownify(renderer_class=Diaspora::Markdownify::HTML)
  49. renderer = renderer_class.new options[:markdown_render_options]
  50. markdown = Redcarpet::Markdown.new renderer, options[:markdown_options]
  51. @message = markdown.render message
  52. end
  53. # In very clear cases, let newlines become <br /> tags
  54. # Grabbed from Github flavored Markdown
  55. def process_newlines
  56. message.gsub(/^[\w\<][^\n]*\n+/) do |x|
  57. x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
  58. end
  59. end
  60. def escape_mentions_for_markdown
  61. @message = Diaspora::Mentionable.escape_for_markdown(message)
  62. end
  63. def render_mentions
  64. unless options[:disable_hovercards] || options[:mentioned_people].empty?
  65. @message = Diaspora::Mentionable.format message, options[:mentioned_people]
  66. end
  67. if options[:disable_hovercards]
  68. @message = Diaspora::Mentionable.filter_people(message, [], absolute_links: true)
  69. else
  70. make_mentions_plain_text
  71. end
  72. end
  73. def make_mentions_plain_text
  74. @message = Diaspora::Mentionable.format message, options[:mentioned_people], plain_text: true
  75. end
  76. def render_tags
  77. @message = Diaspora::Taggable.format_tags message, no_escape: !options[:escape_tags]
  78. end
  79. def camo_urls
  80. @message = Diaspora::Camo.from_markdown(@message)
  81. end
  82. def normalize
  83. @message = self.class.normalize(@message)
  84. end
  85. def diaspora_links
  86. @message = @message.gsub(DiasporaFederation::Federation::DiasporaUrlParser::DIASPORA_URL_REGEX) {|match_str|
  87. guid = Regexp.last_match(3)
  88. Regexp.last_match(2) == "post" && Post.exists?(guid: guid) ? AppConfig.url_to("/posts/#{guid}") : match_str
  89. }
  90. end
  91. end
  92. DEFAULTS = {mentioned_people: [],
  93. disable_hovercards: false,
  94. truncate: false,
  95. append: nil,
  96. append_after_truncate: nil,
  97. squish: false,
  98. escape: true,
  99. escape_tags: false,
  100. markdown_options: {
  101. autolink: true,
  102. fenced_code_blocks: true,
  103. space_after_headers: true,
  104. strikethrough: true,
  105. footnotes: true,
  106. tables: true,
  107. no_intra_emphasis: true
  108. },
  109. markdown_render_options: {
  110. filter_html: true,
  111. hard_wrap: true,
  112. safe_links_only: true
  113. }}.freeze
  114. delegate :empty?, :blank?, :present?, to: :raw
  115. # @param [String] text Raw input text
  116. # @param [Hash] opts Global options affecting output
  117. # @option opts [Array<Person>] :mentioned_people ([]) List of people
  118. # allowed to mention
  119. # @option opts [Boolean] :disable_hovercards (true) Render all mentions
  120. # as absolute profile links. This ignores :mentioned_people
  121. # @option opts [#to_i, Boolean] :truncate (false) Truncate message to
  122. # the specified length
  123. # @option opts [String] :append (nil) Append text to the end of
  124. # the (truncated) message, counts into truncation length
  125. # @option opts [String] :append_after_truncate (nil) Append text to the end
  126. # of the (truncated) message, doesn't count into the truncation length
  127. # @option opts [Boolean] :squish (false) Squish the message, that is
  128. # remove all surrounding and consecutive whitespace
  129. # @option opts [Boolean] :escape (true) Escape HTML relevant characters
  130. # in the message. Note that his option is ignored in the plaintext
  131. # renderers.
  132. # @option opts [Boolean] :escape_tags (false) Escape HTML relevant
  133. # characters in tags when rendering them
  134. # @option opts [Hash] :markdown_options Override default options passed
  135. # to Redcarpet
  136. # @option opts [Hash] :markdown_render_options Override default options
  137. # passed to the Redcarpet renderer
  138. def initialize(text, opts={})
  139. @text = text
  140. @options = DEFAULTS.deep_merge opts
  141. end
  142. # @param [Hash] opts Override global output options, see {#initialize}
  143. def plain_text opts={}
  144. process(opts) {
  145. make_mentions_plain_text
  146. diaspora_links
  147. squish
  148. append_and_truncate
  149. }
  150. end
  151. # @param [Hash] opts Override global output options, see {#initialize}
  152. def plain_text_without_markdown opts={}
  153. process(opts) {
  154. make_mentions_plain_text
  155. diaspora_links
  156. strip_markdown
  157. squish
  158. append_and_truncate
  159. }
  160. end
  161. # @param [Hash] opts Override global output options, see {#initialize}
  162. def plain_text_for_json opts={}
  163. process(opts) {
  164. normalize
  165. diaspora_links
  166. camo_urls if AppConfig.privacy.camo.proxy_markdown_images?
  167. }
  168. end
  169. # @param [Hash] opts Override global output options, see {#initialize}
  170. def html opts={}
  171. process(opts) {
  172. escape
  173. normalize
  174. diaspora_links
  175. render_mentions
  176. render_tags
  177. squish
  178. append_and_truncate
  179. }.html_safe # rubocop:disable Rails/OutputSafety
  180. end
  181. # @param [Hash] opts Override global output options, see {#initialize}
  182. def markdownified opts={}
  183. process(opts) {
  184. process_newlines
  185. normalize
  186. diaspora_links
  187. camo_urls if AppConfig.privacy.camo.proxy_markdown_images?
  188. escape_mentions_for_markdown
  189. markdownify
  190. render_mentions
  191. render_tags
  192. squish
  193. append_and_truncate
  194. }.html_safe # rubocop:disable Rails/OutputSafety
  195. end
  196. def markdownified_for_mail
  197. process(disable_hovercards: true) {
  198. process_newlines
  199. normalize
  200. diaspora_links
  201. camo_urls if AppConfig.privacy.camo.proxy_markdown_images?
  202. render_mentions
  203. markdownify(Diaspora::Markdownify::Email)
  204. squish
  205. append_and_truncate
  206. }.html_safe # rubocop:disable Rails/OutputSafety
  207. end
  208. # Get a short summary of the message
  209. # @param [Hash] opts Additional options
  210. # @option opts [Integer] :length (70) Truncate the title to
  211. # this length. If not given defaults to 70.
  212. def title opts={}
  213. # Setext-style header
  214. heading = if /\A(?<setext_content>.{1,200})\n(?:={1,200}|-{1,200})(?:\r?\n|$)/ =~ @text.lstrip
  215. setext_content
  216. # Atx-style header
  217. elsif /\A\#{1,6}\s+(?<atx_content>.{1,200}?)(?:\s+#+)?(?:\r?\n|$)/ =~ @text.lstrip
  218. atx_content
  219. end
  220. heading &&= self.class.new(heading).plain_text_without_markdown
  221. if heading
  222. heading.truncate opts.fetch(:length, 70)
  223. else
  224. plain_text_without_markdown squish: true, truncate: opts.fetch(:length, 70)
  225. end
  226. end
  227. # Extracts all the urls from the raw message and return them in the form of a string
  228. # Different URLs are seperated with a space
  229. def urls
  230. @urls ||= Twitter::TwitterText::Extractor.extract_urls(plain_text_without_markdown).map {|url|
  231. Addressable::URI.parse(url).normalize.to_s
  232. }
  233. end
  234. def raw
  235. @text
  236. end
  237. def to_s
  238. plain_text
  239. end
  240. private
  241. def process(opts, &block)
  242. Processor.process(@text, @options.deep_merge(opts), &block)
  243. end
  244. end
  245. end

lib/diaspora/relayable.rb

96.3% lines covered

27 relevant lines. 26 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Diaspora
  6. 1 module Relayable
  7. 1 def self.included(model)
  8. 3 model.class_eval do
  9. 3 validates :parent, presence: true
  10. 3 validates_associated :parent
  11. 3 validate :author_is_not_ignored
  12. 3 delegate :public?, to: :parent
  13. 3 delegate :author, :diaspora_handle, to: :parent, prefix: true
  14. end
  15. end
  16. 1 def root
  17. 3792 @root ||= parent
  18. 3792 @root = @root.parent while @root.is_a?(Relayable)
  19. 3792 @root
  20. end
  21. 1 def author_is_not_ignored
  22. 1236 unless new_record? && root.present? && root.author.local? &&
  23. root.author.owner.ignored_people.include?(author)
  24. 1231 return
  25. end
  26. 5 errors.add(:author_id, "This relayable author is ignored by the post author")
  27. end
  28. # @return [Array<Person>]
  29. 1 def subscribers
  30. 89 if root.author.local?
  31. 83 if author.local?
  32. 46 root.subscribers
  33. else
  34. 58 root.subscribers.select(&:remote?).reject {|person| person.pod_id == author.pod_id }
  35. end
  36. else
  37. 6 [root.author, author]
  38. end
  39. end
  40. 1 def sender_for_dispatch
  41. 38 root.author.owner if root.author.local?
  42. end
  43. # @abstract
  44. 1 def parent
  45. raise NotImplementedError.new('you must override parent in order to enable relayable on this model')
  46. end
  47. end
  48. end

lib/diaspora/shareable.rb

100.0% lines covered

40 relevant lines. 40 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. # the point of this object is to centralize the simmilarities of Photo and Post,
  6. # as they used to be the same class
  7. 1 module Diaspora
  8. 1 module Shareable
  9. 1 def self.included(model)
  10. 2 model.instance_eval do
  11. 2 include Diaspora::Fields::Guid
  12. 2 include Diaspora::Fields::Author
  13. 2 has_many :aspect_visibilities, as: :shareable, validate: false, dependent: :delete_all
  14. 2 has_many :aspects, through: :aspect_visibilities
  15. 2 has_many :share_visibilities, as: :shareable, dependent: :delete_all
  16. 2 delegate :id, :name, :first_name, to: :author, prefix: true
  17. # scopes
  18. 2 scope :with_visibility, -> {
  19. 189 joins("LEFT OUTER JOIN share_visibilities ON share_visibilities.shareable_id = #{table_name}.id AND "\
  20. "share_visibilities.shareable_type = '#{base_class}'")
  21. }
  22. 2 scope :with_aspects, -> {
  23. 54 joins("LEFT OUTER JOIN aspect_visibilities ON aspect_visibilities.shareable_id = #{table_name}.id AND "\
  24. " aspect_visibilities.shareable_type = '#{base_class}'")
  25. }
  26. end
  27. 2 model.extend Diaspora::Shareable::QueryMethods
  28. end
  29. 1 def receive(recipient_user_ids)
  30. 1491 return if recipient_user_ids.empty? || public?
  31. 795 ShareVisibility.batch_import(recipient_user_ids, self)
  32. end
  33. # The list of people that should receive this Shareable.
  34. #
  35. # @return [Array<Person>] The list of subscribers to this shareable
  36. 1 def subscribers
  37. 1826 user = author.owner
  38. 1826 if public?
  39. 765 [*user.contact_people, author]
  40. else
  41. 1061 user.people_in_aspects(user.aspects_with_shareable(self.class, id))
  42. end
  43. end
  44. # Remote pods which are known to be subscribed to the post. Must include all pods which received the post in the
  45. # past.
  46. #
  47. # @return [Array<String>] The list of pods' URIs
  48. 1 def subscribed_pods_uris
  49. 20 Pod.find(subscribers.select(&:remote?).map(&:pod_id).uniq).map {|pod| pod.url_to("") }
  50. end
  51. 1 module QueryMethods
  52. 1 def owned_or_visible_by_user(user)
  53. 64 with_visibility.where(
  54. visible_by_user(user).or(arel_table[:public].eq(true)
  55. .or(arel_table[:author_id].eq(user.person_id)))
  56. ).select("DISTINCT #{table_name}.*")
  57. end
  58. 1 def from_person_visible_by_user(user, person)
  59. 64 return owned_by_user(user) if person == user.person
  60. 43 with_visibility.where(author_id: person.id).where(
  61. visible_by_user(user).or(arel_table[:public].eq(true))
  62. ).select("DISTINCT #{table_name}.*")
  63. end
  64. 1 def for_visible_shareable_sql(max_time, order, limit=15, types=Stream::Base::TYPES_OF_POST_IN_STREAM)
  65. 139 by_max_time(max_time, order).order(table_name + ".id DESC").where(type: types).limit(limit)
  66. end
  67. 1 def by_max_time(max_time, order="created_at")
  68. 182 where("#{table_name}.#{order} < ?", max_time).order("#{table_name}.#{order} DESC")
  69. end
  70. 1 def owned_by_user(user)
  71. 21 user.person.public_send(table_name)
  72. end
  73. 1 private
  74. 1 def visible_by_user(user)
  75. 107 ShareVisibility.arel_table[:user_id].eq(user.id)
  76. end
  77. end
  78. end
  79. end

lib/diaspora/signature.rb

100.0% lines covered

10 relevant lines. 10 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 module Diaspora
  3. 1 module Signature
  4. 1 def self.included(model)
  5. 3 model.class_eval do
  6. 3 belongs_to :signature_order
  7. 3 validates :signature_order, presence: true
  8. 3 validates :author_signature, presence: true
  9. 3 serialize :additional_data, Hash
  10. 3 def order
  11. 16 signature_order.order.split
  12. end
  13. end
  14. end
  15. end
  16. end

lib/diaspora/taggable.rb

100.0% lines covered

34 relevant lines. 34 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. 1 module Diaspora
  6. 1 module Taggable
  7. 1 def self.included(model)
  8. 3 model.class_eval do
  9. 3 cattr_accessor :field_with_tags
  10. # validate tag's name maximum length [tag's name should be less than or equal to 255 chars]
  11. 3 validate :tag_name_max_length, on: :create
  12. # tag's name is limited to 255 charchters according to ActsAsTaggableOn gem, so we check the length of the name for each tag
  13. 3 def tag_name_max_length
  14. 10364 tag_list.each do |tag|
  15. 15074 errors.add(:tags, I18n.t("tags.name_too_long", count: 255, current_length: tag.length)) if tag.length > 255
  16. end
  17. end
  18. 3 protected :tag_name_max_length
  19. end
  20. 3 model.instance_eval do
  21. 3 before_validation :build_tags # build tags before validation fixs the too long tag name issue #5737
  22. 3 def extract_tags_from sym
  23. 3 self.field_with_tags = sym
  24. end
  25. 3 def field_with_tags_setter
  26. 129 "#{self.field_with_tags}=".to_sym
  27. end
  28. end
  29. end
  30. 1 def build_tags
  31. 42275 self.tag_list = tag_strings
  32. end
  33. 1 def tag_strings
  34. 42389 MessageRenderer::Processor.normalize(send(self.class.field_with_tags) || "")
  35. .scan(/(?:^|\s)#([#{ActsAsTaggableOn::Tag.tag_text_regexp}]+|<3)/u)
  36. .map(&:first)
  37. .uniq(&:downcase)
  38. end
  39. 1 def self.format_tags(text, opts={})
  40. 231 return text if opts[:plain_text]
  41. 228 text = ERB::Util.h(text) unless opts[:no_escape]
  42. 228 regex =/(^|\s|>)#([#{ActsAsTaggableOn::Tag.tag_text_regexp}]+|&lt;3)/u
  43. 228 text.to_str.gsub(regex) { |matched_string|
  44. 192 pre, url_bit, clickable = $1, $2, "##{$2}"
  45. 192 if $2 == '&lt;3'
  46. # Special case for love, because the world needs more love.
  47. 10 url_bit = '<3'
  48. end
  49. 192 %{#{pre}<a class="tag" href="/tags/#{url_bit}">#{clickable}</a>}
  50. }.html_safe
  51. end
  52. 1 def self.format_tags_for_mail(text)
  53. 203 regex = /(?<=^|\s|>)#([#{ActsAsTaggableOn::Tag.tag_text_regexp}]+|<3)/u
  54. 203 text.gsub(regex) do |tag|
  55. 17 opts = {name: ActsAsTaggableOn::Tag.normalize(tag)}
  56. .merge(Rails.application.config.action_mailer.default_url_options)
  57. 17 "[#{tag}](#{Rails.application.routes.url_helpers.tag_url(opts)})"
  58. end
  59. end
  60. end
  61. end

lib/direction_detector.rb

0.0% lines covered

17 relevant lines. 0 lines covered and 17 lines missed.
    
  1. # coding: utf-8
  2. # frozen_string_literal: true
  3. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  4. # licensed under the Affero General Public License version 3 or later. See
  5. # the COPYRIGHT file.
  6. class String
  7. RTL_CLEANER_REGEXES = [ /@[^ ]+|#[^ ]+/u, # mention, tag
  8. /^RT[: ]{1}| RT | RT: |[♺♻:]/u # retweet
  9. ]
  10. def is_rtl?
  11. return false if self.strip.empty?
  12. detector = StringDirection::Detector.new(:dominant)
  13. detector.rtl? self
  14. end
  15. # Diaspora specific
  16. def cleaned_is_rtl?
  17. string = String.new(self)
  18. RTL_CLEANER_REGEXES.each do |cleaner|
  19. string.gsub!(cleaner, '')
  20. end
  21. string.is_rtl?
  22. end
  23. end

lib/email_inviter.rb

0.0% lines covered

24 relevant lines. 0 lines covered and 24 lines missed.
    
  1. # frozen_string_literal: true
  2. class EmailInviter
  3. attr_accessor :emails, :inviter, :locale
  4. def initialize(emails, inviter, options={})
  5. options = options.symbolize_keys
  6. self.locale = options.fetch(:locale, 'en')
  7. self.inviter = inviter
  8. self.emails = emails
  9. end
  10. def emails=(list)
  11. emails = list.split(%r{[,\s]+})
  12. emails.reject!{|x| x == inviter.email } unless inviter.nil?
  13. @emails = emails
  14. end
  15. def invitation_code
  16. @invitation_code ||= inviter.invitation_code
  17. end
  18. def send!
  19. self.emails.each{ |email| mail(email)}
  20. end
  21. private
  22. def mail(email)
  23. Notifier.invite(email, inviter, invitation_code, locale).deliver_now
  24. end
  25. end

lib/error_page_renderer.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. # Inspired by https://github.com/route/errgent/blob/master/lib/errgent/renderer.rb
  3. class ErrorPageRenderer
  4. def initialize options={}
  5. @codes = options.fetch :codes, [404, 500]
  6. @output = options.fetch :output, "public/%s.html"
  7. @template = options.fetch :template, "errors/error_%s"
  8. @layout = options.fetch :layout, "layouts/error_page"
  9. end
  10. def render
  11. @codes.each do |code|
  12. path = Rails.root.join(@output % code)
  13. File.write path, ApplicationController.render(@template % code, layout: @layout, locals: {code: code})
  14. end
  15. end
  16. end

lib/evil_query.rb

0.0% lines covered

99 relevant lines. 0 lines covered and 99 lines missed.
    
  1. # frozen_string_literal: true
  2. module EvilQuery
  3. class Base
  4. include Diaspora::Logging
  5. def fetch_ids!(relation, id_column)
  6. #the relation should be ordered and limited by here
  7. @class.connection.select_values(id_sql(relation, id_column))
  8. end
  9. def id_sql(relation, id_column)
  10. @class.connection.unprepared_statement { relation.select(id_column).to_sql }
  11. end
  12. end
  13. class Participation < Base
  14. def initialize(user)
  15. @user = user
  16. @class = Post
  17. end
  18. def posts
  19. author_id = @user.person_id
  20. Post.joins("LEFT OUTER JOIN participations ON participations.target_id = posts.id AND " \
  21. "participations.target_type = 'Post'")
  22. .where(::Participation.arel_table[:author_id].eq(author_id).or(Post.arel_table[:author_id].eq(author_id)))
  23. .order("posts.interacted_at DESC")
  24. .distinct
  25. end
  26. end
  27. class LikedPosts < Base
  28. def initialize(user)
  29. @user = user
  30. end
  31. def posts
  32. Post.liked_by(@user.person)
  33. end
  34. end
  35. class CommentedPosts < Base
  36. def initialize(user)
  37. @user = user
  38. end
  39. def posts
  40. Post.commented_by(@user.person)
  41. end
  42. end
  43. class MultiStream < Base
  44. def initialize(user, order, max_time, include_spotlight)
  45. @user = user
  46. @class = Post
  47. @order = order
  48. @max_time = max_time
  49. @include_spotlight = include_spotlight
  50. end
  51. def make_relation!
  52. logger.debug("[EVIL-QUERY] make_relation!")
  53. post_ids = aspects_post_ids! + ids!(followed_tags_posts!) + ids!(mentioned_posts)
  54. post_ids += ids!(community_spotlight_posts!) if @include_spotlight
  55. Post.where(:id => post_ids)
  56. end
  57. def aspects_post_ids!
  58. logger.debug("[EVIL-QUERY] aspect_post_ids!")
  59. @user.visible_shareable_ids(Post, limit: 15, order: "#{@order} DESC", max_time: @max_time, all_aspects?: true)
  60. end
  61. def followed_tags_posts!
  62. logger.debug("[EVIL-QUERY] followed_tags_posts!")
  63. StatusMessage.public_tag_stream(@user.followed_tag_ids).excluding_hidden_content(@user)
  64. end
  65. def mentioned_posts
  66. logger.debug("[EVIL-QUERY] mentioned_posts")
  67. StatusMessage.where_person_is_mentioned(@user.person)
  68. end
  69. def community_spotlight_posts!
  70. Post.all_public.where(:author_id => fetch_ids!(Person.community_spotlight, 'people.id'))
  71. end
  72. def ids!(query)
  73. fetch_ids!(query.for_a_stream(@max_time, @order), 'posts.id')
  74. end
  75. end
  76. class VisibleShareableById < Base
  77. def initialize(user, klass, key, id, conditions={})
  78. @querent = user
  79. @class = klass
  80. @key = key
  81. @id = id
  82. @conditions = conditions
  83. end
  84. def post!
  85. #small optimization - is this optimal order??
  86. querent_has_visibility.first || querent_is_author.first || public_post.first
  87. end
  88. protected
  89. def querent_has_visibility
  90. @class.where(@key => @id).joins(:share_visibilities)
  91. .where(share_visibilities: {user_id: @querent.id})
  92. .where(@conditions)
  93. .select(@class.table_name + ".*")
  94. end
  95. def querent_is_author
  96. @class.where(@key => @id, :author_id => @querent.person.id).where(@conditions)
  97. end
  98. def public_post
  99. @class.where(@key => @id, :public => true).where(@conditions)
  100. end
  101. end
  102. end

lib/i18n_interpolation_fallbacks.rb

0.0% lines covered

21 relevant lines. 0 lines covered and 21 lines missed.
    
  1. # frozen_string_literal: true
  2. module I18n
  3. module Backend
  4. module InterpolationFallbacks
  5. def translate(locale, key, options = {})
  6. default = extract_non_symbol_default!(options) if options[:default]
  7. options.merge!(:default => default) if default
  8. original_exception = nil
  9. I18n.fallbacks[locale].each do |fallback|
  10. begin
  11. result = super(fallback, key, options)
  12. return result unless result.nil?
  13. rescue I18n::MissingInterpolationArgument, I18n::InvalidPluralizationData => e
  14. original_exception ||= e
  15. end
  16. end
  17. return super(locale, nil, options) if default
  18. raise original_exception
  19. end
  20. end
  21. end
  22. end

lib/node_info.rb

100.0% lines covered

77 relevant lines. 77 lines covered and 0 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 require "pathname"
  3. 1 require "json-schema"
  4. 1 module NodeInfo
  5. 1 VERSIONS = %w[1.0 2.0 2.1].freeze
  6. 1 SCHEMAS = {} # rubocop:disable Style/MutableConstant
  7. 1 private_constant :SCHEMAS
  8. 1 Document = Struct.new(:version, :software, :protocols, :services, :open_registrations, :usage, :metadata) do
  9. # rubocop:disable Lint/ConstantDefinitionInBlock
  10. 1 Software = Struct.new(:name, :version, :repository, :homepage) do
  11. 1 def initialize(name=nil, version=nil)
  12. 19 super(name, version)
  13. end
  14. 1 def version_10_hash
  15. {
  16. 39 "name" => name,
  17. "version" => version
  18. }
  19. end
  20. 1 def version_21_hash
  21. 8 version_10_hash.merge(
  22. "repository" => repository,
  23. "homepage" => homepage
  24. )
  25. end
  26. end
  27. 1 Protocols = Struct.new(:protocols) do
  28. 1 def initialize(protocols=[])
  29. 19 super(protocols)
  30. end
  31. 1 def version_10_hash
  32. {
  33. 21 "inbound" => protocols,
  34. "outbound" => protocols
  35. }
  36. end
  37. 1 def version_20_array
  38. 18 protocols
  39. end
  40. end
  41. 1 Services = Struct.new(:inbound, :outbound) do
  42. 1 def initialize(inbound=[], outbound=[])
  43. 19 super(inbound, outbound)
  44. end
  45. 1 def version_10_hash
  46. {
  47. 39 "inbound" => inbound,
  48. "outbound" => outbound
  49. }
  50. end
  51. end
  52. 1 Usage = Struct.new(:users, :local_posts, :local_comments) do
  53. 1 Users = Struct.new(:total, :active_halfyear, :active_month) do
  54. 1 def initialize(total=nil, active_halfyear=nil, active_month=nil)
  55. 19 super(total, active_halfyear, active_month)
  56. end
  57. 1 def version_10_hash
  58. {
  59. 39 "total" => total,
  60. "activeHalfyear" => active_halfyear,
  61. "activeMonth" => active_month
  62. }
  63. end
  64. end
  65. 1 def initialize(local_posts=nil, local_comments=nil)
  66. 19 super(Users.new, local_posts, local_comments)
  67. end
  68. 1 def version_10_hash
  69. {
  70. 39 "users" => users.version_10_hash,
  71. "localPosts" => local_posts,
  72. "localComments" => local_comments
  73. }
  74. end
  75. end
  76. # rubocop:enable Lint/ConstantDefinitionInBlock
  77. 1 def self.build
  78. 19 new.tap do |doc|
  79. 19 yield doc
  80. 19 doc.validate
  81. end
  82. end
  83. 1 def initialize(version=nil, open_registrations=nil, metadata={})
  84. 19 super(version, Software.new, Protocols.new, Services.new, open_registrations, Usage.new, metadata)
  85. end
  86. 1 def as_json(_options={})
  87. 39 case version
  88. when "1.0"
  89. 21 version_10_hash
  90. when "2.0"
  91. 10 version_20_hash
  92. when "2.1"
  93. 8 version_21_hash
  94. end
  95. end
  96. 1 def content_type
  97. 6 "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/#{version}#"
  98. end
  99. 1 def schema
  100. 19 NodeInfo.schema version
  101. end
  102. 1 def validate
  103. 19 assert NodeInfo.supported_version?(version), "Unknown version #{version}"
  104. 19 JSON::Validator.validate!(schema, as_json)
  105. end
  106. 1 private
  107. 1 def assert(condition, message)
  108. 19 raise ArgumentError, message unless condition
  109. end
  110. 1 def version_10_hash
  111. 21 deep_compact(
  112. "version" => "1.0",
  113. "software" => software.version_10_hash,
  114. "protocols" => protocols.version_10_hash,
  115. "services" => services.version_10_hash,
  116. "openRegistrations" => open_registrations,
  117. "usage" => usage.version_10_hash,
  118. "metadata" => metadata
  119. )
  120. end
  121. 1 def version_20_hash
  122. 10 deep_compact(
  123. "version" => "2.0",
  124. "software" => software.version_10_hash,
  125. "protocols" => protocols.version_20_array,
  126. "services" => services.version_10_hash,
  127. "openRegistrations" => open_registrations,
  128. "usage" => usage.version_10_hash,
  129. "metadata" => metadata
  130. )
  131. end
  132. 1 def version_21_hash
  133. 8 deep_compact(
  134. "version" => "2.1",
  135. "software" => software.version_21_hash,
  136. "protocols" => protocols.version_20_array,
  137. "services" => services.version_10_hash,
  138. "openRegistrations" => open_registrations,
  139. "usage" => usage.version_10_hash,
  140. "metadata" => metadata
  141. )
  142. end
  143. 1 def deep_compact(hash)
  144. 286 hash.tap do |hash|
  145. 286 hash.reject! {|_, value|
  146. 892 deep_compact value if value.is_a? Hash
  147. 892 value.nil?
  148. }
  149. end
  150. end
  151. end
  152. 1 def self.schema(version)
  153. 24 SCHEMAS[version] ||= JSON.parse(
  154. Pathname.new(__dir__).join("..", "vendor", "nodeinfo", "schemas", "#{version}.json").expand_path.read
  155. )
  156. end
  157. 1 def self.build(&block)
  158. 19 Document.build(&block)
  159. end
  160. 1 def self.jrd(endpoint)
  161. {
  162. 2 "links" => VERSIONS.map {|version|
  163. {
  164. 6 "rel" => "http://nodeinfo.diaspora.software/ns/schema/#{version}",
  165. "href" => endpoint % {version: version}
  166. }
  167. }
  168. }
  169. end
  170. 1 def self.supported_version?(version)
  171. 29 VERSIONS.include? version
  172. end
  173. end

lib/photo_exporter.rb

95.83% lines covered

24 relevant lines. 23 lines covered and 1 lines missed.
    
  1. # frozen_string_literal: true
  2. 1 class PhotoExporter
  3. 1 attr_reader :user
  4. 1 def initialize(user)
  5. 10 @user = user
  6. end
  7. 1 def perform
  8. 9 temp_zip = Tempfile.new([user.username, "_photos.zip"])
  9. begin
  10. 9 Zip::OutputStream.open(temp_zip.path) do |zip_output_stream|
  11. 9 user.photos.each do |photo|
  12. 4 export_photo(zip_output_stream, photo)
  13. end
  14. end
  15. ensure
  16. 9 temp_zip.close
  17. end
  18. 9 update_exported_photos_at(temp_zip)
  19. end
  20. 1 private
  21. 1 def export_photo(zip_output_stream, photo)
  22. 4 photo_file = photo.unprocessed_image.file
  23. 4 if photo_file
  24. 4 photo_data = photo_file.read
  25. 3 zip_output_stream.put_next_entry(photo.remote_photo_name)
  26. 3 zip_output_stream.print(photo_data)
  27. else
  28. user.logger.info "Export photos error: No file for #{photo.remote_photo_name} not found"
  29. end
  30. rescue Errno::ENOENT
  31. 1 user.logger.info "Export photos error: #{photo.unprocessed_image.file.path} not found"
  32. end
  33. 1 def update_exported_photos_at(temp_zip)
  34. 9 user.update exported_photos_file: temp_zip, exported_photos_at: Time.zone.now
  35. ensure
  36. 9 user.restore_attributes if user.invalid?
  37. 9 user.update exporting_photos: false
  38. end
  39. end

lib/publisher.rb

0.0% lines covered

9 relevant lines. 0 lines covered and 9 lines missed.
    
  1. # frozen_string_literal: true
  2. class Publisher
  3. attr_accessor :user, :open, :prefill, :public
  4. def initialize(user, opts={})
  5. self.user = user
  6. self.open = opts[:open]
  7. self.prefill = opts[:prefill]
  8. self.public = opts[:public]
  9. end
  10. end

lib/pubsubhubbub.rb

0.0% lines covered

12 relevant lines. 0 lines covered and 12 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Pubsubhubbub
  6. def initialize(hub, options={})
  7. @hub = hub
  8. end
  9. def publish(feed)
  10. conn = Faraday.new do |c|
  11. c.use Faraday::Request::UrlEncoded # encode request params as "www-form-urlencoded"
  12. c.adapter Faraday::Adapter::NetHttp # perform requests with Net::HTTP
  13. end
  14. conn.post @hub, {'hub.url' => feed, 'hub.mode' => 'publish'}
  15. end
  16. end

lib/share_visibility_converter.rb

0.0% lines covered

47 relevant lines. 0 lines covered and 47 lines missed.
    
  1. # frozen_string_literal: true
  2. #we dont have the environment, and it is not carring over from the migration
  3. unless defined?(Person)
  4. class Person < ApplicationRecord
  5. belongs_to :owner, :class_name => 'User'
  6. end
  7. end
  8. unless defined?(User)
  9. class User < ApplicationRecord
  10. serialize :hidden_shareables, Hash
  11. end
  12. end
  13. unless defined?(Contact)
  14. class Contact < ApplicationRecord
  15. belongs_to :user
  16. end
  17. end
  18. unless defined?(ShareVisibility)
  19. class ShareVisibility < ApplicationRecord
  20. belongs_to :contact
  21. end
  22. end
  23. class ShareVisibilityConverter
  24. RECENT = 2 # number of weeks to do in the migration
  25. def self.copy_hidden_share_visibilities_to_users(only_recent = false)
  26. query = ShareVisibility.where(:hidden => true).includes(:contact => :user)
  27. query = query.where('share_visibilities.updated_at > ?', RECENT.weeks.ago) if only_recent
  28. count = query.count
  29. puts "Updating #{count} records in batches of 1000..."
  30. batch_count = 1
  31. query.find_in_batches do |visibilities|
  32. puts "Updating batch ##{batch_count} of #{(count/1000)+1}..."
  33. batch_count += 1
  34. visibilities.each do |visibility|
  35. begin
  36. type = visibility.shareable_type
  37. id = visibility.shareable_id.to_s
  38. u = visibility.contact.user
  39. u.hidden_shareables ||= {}
  40. u.hidden_shareables[type] ||= []
  41. u.hidden_shareables[type] << id unless u.hidden_shareables[type].include?(id)
  42. u.save!(:validate => false)
  43. rescue => e
  44. puts "ERROR: #{e.message} skipping pv with id: #{visibility.id}"
  45. end
  46. end
  47. end
  48. end
  49. end

lib/sidekiq_middlewares.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. module SidekiqMiddlewares
  3. class CleanAndShortBacktraces
  4. def call(worker, item, queue)
  5. yield
  6. rescue Exception
  7. backtrace = Rails.backtrace_cleaner.clean($!.backtrace)
  8. backtrace.reject! { |line| line =~ /lib\/sidekiq_middlewares.rb/ }
  9. limit = AppConfig.environment.sidekiq.backtrace.get
  10. limit = limit ? limit.to_i : 0
  11. backtrace = [] if limit == 0
  12. raise $!, $!.message, backtrace[0..limit]
  13. end
  14. end
  15. end

lib/stream.rb

0.0% lines covered

13 relevant lines. 0 lines covered and 13 lines missed.
    
  1. # frozen_string_literal: true
  2. module Stream
  3. require "stream/activity"
  4. require "stream/aspect"
  5. require "stream/comments"
  6. require "stream/followed_tag"
  7. require "stream/likes"
  8. require "stream/mention"
  9. require "stream/multi"
  10. require "stream/person"
  11. require "stream/public"
  12. require "stream/local_public"
  13. require "stream/tag"
  14. end

lib/stream/activity.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. class Stream::Activity < Stream::Base
  3. def link(opts={})
  4. Rails.application.routes.url_helpers.activity_streams_path(opts)
  5. end
  6. def order
  7. "interacted_at"
  8. end
  9. def title
  10. I18n.translate("streams.activity.title")
  11. end
  12. # @return [ActiveRecord::Association<Post>] AR association of posts
  13. def posts
  14. @posts ||= EvilQuery::Participation.new(user).posts
  15. end
  16. end

lib/stream/aspect.rb

0.0% lines covered

46 relevant lines. 0 lines covered and 46 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Stream::Aspect < Stream::Base
  6. # @param user [User]
  7. # @param inputted_aspect_ids [Array<Integer>] Ids of aspects for given stream
  8. # @param aspect_ids [Array<Integer>] Aspects this stream is responsible for
  9. # @opt max_time [Integer] Unix timestamp of stream's post ceiling
  10. # @opt order [String] Order of posts (i.e. 'created_at', 'updated_at')
  11. # @return [void]
  12. def initialize(user, inputted_aspect_ids, opts={})
  13. super(user, opts)
  14. @inputted_aspect_ids = inputted_aspect_ids
  15. end
  16. # Filters aspects given the stream's aspect ids on initialization and the user.
  17. # Will disclude aspects from inputted aspect ids if user is not associated with their
  18. # target aspects.
  19. #
  20. # @return [ActiveRecord::Association<Aspect>] Filtered aspects given the stream's user
  21. def aspects
  22. @aspects ||= lambda do
  23. a = user.aspects
  24. a = a.where(:id => @inputted_aspect_ids) if @inputted_aspect_ids.any?
  25. a
  26. end.call
  27. end
  28. # @return [ActiveRecord::Association<Post>] AR association of posts
  29. def posts
  30. # NOTE(this should be something like Post.all_for_stream(@user, aspect_ids, {}) that calls visible_shareables
  31. @posts ||= user.visible_shareables(Post, :all_aspects? => for_all_aspects?,
  32. :by_members_of => aspect_ids,
  33. :type => TYPES_OF_POST_IN_STREAM,
  34. :order => "#{order} DESC",
  35. :max_time => max_time
  36. )
  37. end
  38. # @return [ActiveRecord::Association<Person>] AR association of people within stream's given aspects
  39. def people
  40. @people ||= Person.unique_from_aspects(aspect_ids, user).includes(:profile)
  41. end
  42. # @return [String] URL
  43. def link(opts={})
  44. Rails.application.routes.url_helpers.aspects_path(opts)
  45. end
  46. # The first aspect in #aspects, given the stream is not for all aspects, or #aspects size is 1
  47. # @note aspects.first is used for mobile. NOTE(this is a hack and should be fixed)
  48. # @return [Aspect,Symbol]
  49. def aspect
  50. if !for_all_aspects? || aspects.size == 1
  51. aspects.first
  52. end
  53. end
  54. # The title that will display at the top of the stream's
  55. # publisher box.
  56. #
  57. # @return [String]
  58. def title
  59. if self.for_all_aspects?
  60. I18n.t('streams.aspects.title')
  61. else
  62. self.aspects.to_sentence
  63. end
  64. end
  65. # Determine whether or not the stream is displaying across
  66. # all of the user's aspects.
  67. #
  68. # @return [Boolean]
  69. def for_all_aspects?
  70. @all_aspects ||= aspects.size == user.aspects.size
  71. end
  72. private
  73. def aspect_ids
  74. @aspect_ids ||= aspects.map(&:id)
  75. end
  76. end

lib/stream/base.rb

0.0% lines covered

67 relevant lines. 0 lines covered and 67 lines missed.
    
  1. # frozen_string_literal: true
  2. class Stream::Base
  3. TYPES_OF_POST_IN_STREAM = ['StatusMessage', 'Reshare']
  4. attr_accessor :max_time, :order, :user, :publisher
  5. def initialize(user, opts={})
  6. self.user = user
  7. self.max_time = opts[:max_time]
  8. self.order = opts[:order]
  9. self.publisher = Publisher.new(self.user, publisher_opts)
  10. end
  11. #requied to implement said stream
  12. def link(opts={})
  13. 'change me in lib/base_stream.rb!'
  14. end
  15. def post_from_group(post)
  16. []
  17. end
  18. # @return [String]
  19. def title
  20. 'a title'
  21. end
  22. # @return [ActiveRecord::Relation<Post>]
  23. def posts
  24. Post.all
  25. end
  26. # @return [Array<Post>]
  27. def stream_posts
  28. self.posts.for_a_stream(max_time, order, self.user).tap do |posts|
  29. like_posts_for_stream!(posts) #some sql person could probably do this with joins.
  30. end
  31. end
  32. # @return [ActiveRecord::Association<Person>] AR association of people within stream's given aspects
  33. def people
  34. people_ids = self.stream_posts.map{|x| x.author_id}
  35. Person.where(:id => people_ids).
  36. includes(:profile)
  37. end
  38. # @return [Boolean]
  39. def for_all_aspects?
  40. true
  41. end
  42. #NOTE: MBS bad bad methods the fact we need these means our views are foobared. please kill them and make them
  43. #private methods on the streams that need them
  44. def aspects
  45. user.post_default_aspects
  46. end
  47. # @return [Aspect] The first aspect in #aspects
  48. def aspect
  49. aspects.first
  50. end
  51. def max_time=(time_string)
  52. @max_time = Time.at(time_string.to_i) unless time_string.blank?
  53. @max_time ||= (Time.now + 1)
  54. end
  55. def order=(order_string)
  56. @order = order_string
  57. @order ||= 'created_at'
  58. end
  59. protected
  60. # @return [void]
  61. def like_posts_for_stream!(posts)
  62. return posts unless @user
  63. likes = Like.where(:author_id => @user.person_id, :target_id => posts.map(&:id), :target_type => "Post")
  64. like_hash = likes.inject({}) do |hash, like|
  65. hash[like.target_id] = like
  66. hash
  67. end
  68. posts.each do |post|
  69. post.user_like = like_hash[post.id]
  70. end
  71. end
  72. # @return [Hash]
  73. def publisher_opts
  74. {}
  75. end
  76. # Memoizes all Contacts present in the Stream
  77. #
  78. # @return [Array<Contact>]
  79. def contacts_in_stream
  80. @contacts_in_stream ||= Contact.where(:user_id => user.id, :person_id => people.map(&:id)).load
  81. end
  82. end

lib/stream/comments.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Stream::Comments < Stream::Base
  6. def link(opts={})
  7. Rails.application.routes.url_helpers.comment_stream_path(opts)
  8. end
  9. def title
  10. I18n.translate("streams.comment_stream.title")
  11. end
  12. # @return [ActiveRecord::Association<Post>] AR association of posts
  13. def posts
  14. @posts ||= EvilQuery::CommentedPosts.new(user).posts
  15. end
  16. end

lib/stream/followed_tag.rb

0.0% lines covered

21 relevant lines. 0 lines covered and 21 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Stream::FollowedTag < Stream::Base
  6. def link(opts={})
  7. Rails.application.routes.url_helpers.tag_followings_path(opts)
  8. end
  9. def title
  10. I18n.t('streams.followed_tag.title')
  11. end
  12. # @return [ActiveRecord::Association<Post>] AR association of posts
  13. def posts
  14. @posts ||= StatusMessage.user_tag_stream(user, tag_ids)
  15. end
  16. private
  17. def tag_string
  18. @tag_string ||= tags.join(', '){|tag| tag.name}.to_s
  19. end
  20. def tag_ids
  21. tags.map{|x| x.id}
  22. end
  23. def tags
  24. @tags = user.followed_tags
  25. end
  26. end

lib/stream/likes.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Stream::Likes < Stream::Base
  6. def link(opts={})
  7. Rails.application.routes.url_helpers.like_stream_path(opts)
  8. end
  9. def title
  10. I18n.translate("streams.like_stream.title")
  11. end
  12. # @return [ActiveRecord::Association<Post>] AR association of posts
  13. def posts
  14. @posts ||= EvilQuery::LikedPosts.new(user).posts
  15. end
  16. end

lib/stream/local_public.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. # rubocop:disable Style/ClassAndModuleChildren
  3. class Stream::LocalPublic < Stream::Base
  4. def link(opts={})
  5. Rails.application.routes.url_helpers.local_public_stream_path(opts)
  6. end
  7. def title
  8. I18n.t("streams.local_public.title")
  9. end
  10. # @return [ActiveRecord::Association<Post>] AR association of posts
  11. def posts
  12. @posts ||= Post.all_local_public
  13. end
  14. # Override base class method
  15. def aspects
  16. ["public"]
  17. end
  18. end
  19. # rubocop:enable Style/ClassAndModuleChildren

lib/stream/mention.rb

0.0% lines covered

11 relevant lines. 0 lines covered and 11 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Stream::Mention < Stream::Base
  6. def link(opts={})
  7. Rails.application.routes.url_helpers.mentions_path(opts)
  8. end
  9. def title
  10. I18n.translate("streams.mentions.title")
  11. end
  12. # @return [ActiveRecord::Association<Post>] AR association of posts
  13. def posts
  14. @posts ||= StatusMessage.where_person_is_mentioned(self.user.person)
  15. end
  16. end

lib/stream/multi.rb

0.0% lines covered

54 relevant lines. 0 lines covered and 54 lines missed.
    
  1. # frozen_string_literal: true
  2. class Stream::Multi < Stream::Base
  3. # @return [String] URL
  4. def link(opts)
  5. Rails.application.routes.url_helpers.stream_path(opts)
  6. end
  7. # @return [String]
  8. def title
  9. I18n.t('streams.multi.title')
  10. end
  11. def posts
  12. @posts ||= ::EvilQuery::MultiStream.new(user, order, max_time, include_community_spotlight?).make_relation!
  13. end
  14. #emits an enum of the groups which the post appeared
  15. # :spotlight, :aspects, :tags, :mentioned
  16. def post_from_group(post)
  17. streams_included.collect do |source|
  18. is_in?(source, post)
  19. end.compact
  20. end
  21. private
  22. def publisher_opts
  23. if welcome?
  24. {open: true, prefill: publisher_prefill, public: true}
  25. else
  26. {public: user.post_default_public}
  27. end
  28. end
  29. # Generates the prefill for the publisher
  30. #
  31. # @return [String]
  32. def publisher_prefill
  33. prefill = I18n.t("shared.publisher.new_user_prefill.hello", :new_user_tag => I18n.t('shared.publisher.new_user_prefill.newhere'))
  34. if self.user.followed_tags.size > 0
  35. tag_string = self.user.followed_tags.map{|t| "##{t.name}"}.to_sentence
  36. prefill << I18n.t("shared.publisher.new_user_prefill.i_like", :tags => tag_string)
  37. end
  38. if inviter = self.user.invited_by.try(:person)
  39. prefill << I18n.t("shared.publisher.new_user_prefill.invited_by")
  40. prefill << "@{#{inviter.diaspora_handle}}!"
  41. end
  42. prefill
  43. end
  44. # @return [Boolean]
  45. def welcome?
  46. self.user.getting_started
  47. end
  48. # @return [Array<Symbol>]
  49. def streams_included
  50. @streams_included ||= lambda do
  51. array = [:mentioned, :aspects, :followed_tags]
  52. array << :community_spotlight if include_community_spotlight?
  53. array
  54. end.call
  55. end
  56. # @return [Symbol]
  57. def is_in?(sym, post)
  58. if self.send("#{sym.to_s}_post_ids").find{|x| (x == post.id) || (x.to_s == post.id.to_s)}
  59. "#{sym.to_s}_stream".to_sym
  60. end
  61. end
  62. # @return [Boolean]
  63. def include_community_spotlight?
  64. AppConfig.settings.community_spotlight.enable? && user.show_community_spotlight_in_stream?
  65. end
  66. end

lib/stream/person.rb

0.0% lines covered

15 relevant lines. 0 lines covered and 15 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Stream::Person < Stream::Base
  6. attr_accessor :person
  7. def initialize(user, person, opts={})
  8. self.person = person
  9. super(user, opts)
  10. end
  11. # @return [ActiveRecord::Association<Post>] AR association of posts
  12. def posts
  13. @posts ||= user.present? ? user.posts_from(@person) : @person.posts.where(:public => true)
  14. end
  15. # @return [Array<Post>]
  16. def stream_posts
  17. posts.for_a_stream(max_time, order, user, true).tap do |posts|
  18. like_posts_for_stream!(posts) # some sql person could probably do this with joins.
  19. end
  20. end
  21. end

lib/stream/public.rb

0.0% lines covered

14 relevant lines. 0 lines covered and 14 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Stream::Public < Stream::Base
  6. def link(opts={})
  7. Rails.application.routes.url_helpers.public_stream_path(opts)
  8. end
  9. def title
  10. I18n.translate("streams.public.title")
  11. end
  12. # @return [ActiveRecord::Association<Post>] AR association of posts
  13. def posts
  14. @posts ||= Post.all_public
  15. end
  16. # Override base class method
  17. def aspects
  18. ["public"]
  19. end
  20. end

lib/stream/tag.rb

0.0% lines covered

40 relevant lines. 0 lines covered and 40 lines missed.
    
  1. # frozen_string_literal: true
  2. # Copyright (c) 2010-2011, Diaspora Inc. This file is
  3. # licensed under the Affero General Public License version 3 or later. See
  4. # the COPYRIGHT file.
  5. class Stream::Tag < Stream::Base
  6. attr_accessor :tag_name, :people_page , :people_per_page, :public_only
  7. def initialize(user, tag_name, opts={})
  8. self.tag_name = tag_name
  9. self.people_page = opts[:page] || 1
  10. self.people_per_page = 15
  11. self.public_only = opts[:public_only] || false
  12. super(user, opts)
  13. end
  14. def tag
  15. @tag ||= ActsAsTaggableOn::Tag.named(tag_name).first
  16. end
  17. def display_tag_name
  18. @display_tag_name ||= "##{tag_name}"
  19. end
  20. def tagged_people
  21. @people ||= ::Person.profile_tagged_with(tag_name).paginate(:page => people_page, :per_page => people_per_page)
  22. end
  23. def tagged_people_count
  24. @people_count ||= ::Person.profile_tagged_with(tag_name).count
  25. end
  26. def posts
  27. return @posts unless @posts.nil?
  28. if public_only || user.blank?
  29. return @posts = StatusMessage.public_tag_stream(tag.id)
  30. end
  31. @posts = StatusMessage.user_tag_stream(user, tag.id)
  32. end
  33. def stream_posts
  34. return [] unless tag
  35. super
  36. end
  37. def tag_name=(tag_name)
  38. @tag_name = tag_name.downcase.gsub('#', '')
  39. end
  40. private
  41. # @return [Hash]
  42. def publisher_opts
  43. {:open => true}
  44. end
  45. end